ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring AI 自定义MCP Server(天气预报服务):把 endpoint 改到 TaoToken

Spring AI 自定义MCP Server(天气预报服务):把 endpoint 改到 TaoToken

1. 为什么本地 MCP Server 跑通了,模型通道却成了新瓶颈

很多开发者把 Spring AI 自定义 MCP Server 写完之后,第一反应是「工具能跑就行」。我一开始也这么想:@Tool注解加上了,ToolCallbackProvider也注册了,用 Cline 的 stdio 模式连上去,输入经纬度,天气 JSON 确实回来了。但真正把它接到一个稳定的模型调用通道上时,问题才暴露出来——工具是工具,模型是模型,两者之间的那条「通道」如果没配好,整个链路就是断的。

具体场景是这样的:你本地已经有一个 Spring Boot 3.2.5 + JDK 17 的工程,依赖spring-ai-mcp-server-webmvc-spring-boot-starter 1.0.0-M6,里面写了一个WeatherService,通过@Tool暴露getWeatherConditionByLocation方法,底层调 Open-Meteo 的免费天气 API。MCP Server 本身没问题,server.port: 8098也能起来。但当你希望这个 MCP Server 背后的模型调用走统一通道,而不是每个开发者各自填一堆零散的 key 时,application.yml里的base-url和api-key就成了必须一次性改对的地方。

这篇内容面向的就是这类开发者:本地 MCP Server 已经跑通,现在需要把模型调用通道统一到 TaoToken,并且要能验证「改完之后天气工具真的还能被模型正确调用、返回的 JSON 结构没变」。核心检索词是 Spring AI 自定义 MCP Server 天气预报服务的接入配置,关键词覆盖 Spring AI、MCP Server、天气预报服务、base-url、api-key。

先说清楚 TaoToken 在这里的角色。它是一个统一的模型调用通道,提供 OpenAI 兼容的接口形态,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。你不需要改 MCP Server 的工具逻辑,只需要把模型侧(也就是调用方,比如 Cline、或者你自己的 Spring AI ChatClient)的 base-url 指向它,再配一个 API Key。这样团队里每个人用的都是同一条通道,模型 ID 也统一,排查问题时不会出现「你那边能跑我这边 401」的情况。

适合谁看:已经写过至少一个@Tool方法、知道 MCP 的 stdio 和 SSE 两种传输模式、但被多套 key 和多套 base-url 搞烦的 Java 开发者。如果你还没写过 MCP Server,建议先把WeatherService和ToolCallbackProvider那部分补上,再回来看通道切换,否则验证环节会缺少可观测的返回体。

我试过把模型通道和工具通道混在一起配,结果 Cline 那边报local proxy failed,排查了半天才发现是 base-url 写成了带路径的完整地址,而 SDK 自己又拼了一次/v1。所以下面第 3 节给的配置片段,路径和原文保持一致,你直接复制改 key 就行,不要自己加后缀。

2. TaoToken 前置准备:Key、Base URL 与模型 ID 三件套

在动application.yml之前,先把三件套确认清楚:Base URL、API Key、Model ID。这三样缺一个,后面验证请求都会失败,而且报错信息往往指向别处,容易误判。

Base URL 用 https://taotoken.net/api ,注意这里不带任何多余路径。很多 OpenAI 兼容 SDK 会在内部再拼/v1/chat/completions,如果你手动写成https://taotoken.net/api/v1,就会出现双/v1,典型表现是 404 或者local proxy failed。API Key 在控制台的 API Keys 页面创建,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。创建之后复制一次,后面只显示前缀,所以先存到安全的地方。

模型 ID 这块要看你用哪个模型。如果你只是验证 MCP 工具调用链路,选一个支持 function calling / tool use 的模型即可。Cline 里配置的时候,Model ID 要和通道侧一致,不要写别名。比如你选的是某个 Claude 系列或者 GPT 系列,就按控制台文档里给的准确 ID 填。三件套的关系可以这样理解:Base URL 是「门牌号」,API Key 是「门禁卡」,Model ID 是「你要找的人」。门牌号错了进不去小区,门禁卡错了进不去楼,人找错了工具调用可能不触发。

如果你用的是 Claude Code 这类偏编码场景的工具,通道侧也有对应的 Coding Plan 入口:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。它的意义在于长期编码和 Agent 场景下,通道的配额和稳定性更适合持续调用,而不是每次临时填 key。对于 MCP Server 这种会被反复调用的工具服务,建议直接走 Coding Plan,省得中途 key 失效导致天气工具突然不可用。

还有一个前置动作容易被忽略:确认你的 MCP Server 本身能独立返回正确 JSON。也就是说,先不接模型,直接用 curl 或者浏览器访问 Open-Meteo,确认WeatherInfo反序列化没问题。因为一旦接入模型通道后返回体异常,你很难判断是模型没调工具,还是工具调了但 JSON 解析失败。我踩过的坑就是:current字段里weather_code是整数,但我在WeatherInfo里写成了 String,结果工具执行时抛异常,模型侧只看到「工具调用失败」,完全不知道是类型问题。所以前置准备里,先把工具自身的返回体核对一遍。

控制台地址再放一次,方便你创建完 key 后回来核对:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。在里面可以看到调用量、错误码分布,后面第 5 节排查 401 和reading choices时会用到。

3. 可复制配置:application.yml 里 base-url 与 api-key 的改法

这一节是核心操作。你的 Spring Boot 工程里,application.yml原本可能只配了 MCP Server 自己的信息,像这样:

server: port: 8098 spring: ai: mcp: server: name: weather-server version: 0.0.1

现在要加的是模型调用侧的配置。注意,MCP Server 本身不直接调模型,调模型的是「调用方」。所以有两种改法,取决于你的架构:

第一种,如果你的 Spring Boot 工程里同时有ChatClient在调模型(比如你想让模型自动决定是否调用天气工具),那就在同一个application.yml里加 OpenAI 兼容配置:

spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: your-model-id temperature: 0.7 mcp: server: name: weather-server version: 0.0.1

这里base-url就是 https://taotoken.net/api ,不要加/v1。api-key建议用环境变量注入,不要硬编码在 yml 里,避免提交到仓库。model填你在控制台确认过的 Model ID。

第二种,如果你的模型调用发生在 Cline 这类外部客户端,那application.yml只需要保证 MCP Server 正常暴露,模型通道配置在 Cline 的 MCP 设置里。Cline 的配置是 JSON 形态,路径通常在cline_mcp_settings.json,片段如下:

{ "mcpServers": { "weather-server": { "disabled": false, "timeout": 60, "command": "java", "args": ["-jar", "/path/to/weather-server-0.0.1.jar"], "transportType": "stdio", "env": { "OPENAI_BASE_URL": "https://taotoken.net/api", "OPENAI_API_KEY": "你的TaoTokenKey", "OPENAI_MODEL_ID": "your-model-id" } } } }

注意这里的三件套是 Base URL、Key、Model ID 同时出现,缺一不可。transportType用stdio表示本地 jar 启动;如果你用 SSE 模式连远程,就把command/args换成url字段,指向http://localhost:8098/sse之类的地址。

如果你用的是 Codex 这类工具,它的auth.json里也要写全三件套,结构类似:

{ "base_url": "https://taotoken.net/api", "api_key": "你的TaoTokenKey", "model": "your-model-id" }

路径和字段名以你本地实际文件为准,不要照抄字段名到别的工具里。核心原则只有一个:Base URL 是 https://taotoken.net/api ,Key 从控制台来,Model ID 和控制台一致。

改完之后,先别急着启动。检查一遍有没有重复的spring.ai.openai节点,YAML 里重复 key 会导致后者覆盖前者,表现是 base-url 没生效。另外,如果你的工程里用了spring-ai-mcp-server-webmvc-spring-boot-starter,它和spring-ai-openai-spring-boot-starter的版本要对齐,都是1.0.0-M6,版本错配会出现NoSuchMethodError,看起来像通道问题,其实是依赖冲突。

4. 验证请求:启动后调用天气工具并核对返回 JSON

配置改完,启动工程:

mvn spring-boot:run

看到Tomcat started on port(s): 8098和 MCP Server 注册日志后,先做一次不经过模型的直连验证。用 curl 打 SSE 端点,确认工具列表能出来:

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

如果返回里能看到weather-server和getWeatherConditionByLocation,说明 MCP Server 本身没问题。接下来在 Cline 里发起一次真实调用,输入:

北京天气预报: latitude: 39.9042 longitude: 116.4074

预期行为是:模型先决定调用getWeatherConditionByLocation,工具执行后返回一段包含当前天气和未来 7 天预报的文本。返回体里应该能看到类似这样的结构:

{ "current": { "temperature_2m": 26.5, "apparent_temperature": 27.1, "relative_humidity_2m": 58, "precipitation": 0.0, "weather_code": 1, "wind_speed_10m": 12.3, "wind_direction_10m": 180 }, "daily": { "time": ["2025-01-01", "..."], "temperature_2m_max": [28.0, "..."], "temperature_2m_min": [18.0, "..."] } }

核对三个点:第一,current.temperature_2m是数值不是字符串;第二,daily.time数组长度是 7;第三,weather_code能被你的getWeatherDescription正确映射成中文描述。如果这三点都对,说明工具链路和模型通道都通了。

再验证一次模型通道是否真的走了 TaoToken。在控制台的调用记录里,应该能看到这次请求的时间戳和模型 ID。如果控制台没有记录,但 Cline 又返回了结果,那说明模型调用没走你配的通道,可能还在用旧的 key 或本地缓存。这时候清一下 Cline 的会话,重新发起。

如果你想更直接地验证模型对话通道,可以用模型对话入口发一条简单消息:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 。在里面问「北京天气怎么样」,如果它能触发工具调用并返回天气,说明通道和工具已经串起来了。这一步的意义是把「模型侧」和「工具侧」分开验证,避免混在一起排查。

实测下来,最容易出问题的是timeout。MCP 工具调用如果超过 60 秒,Cline 会断开,表现是「工具无响应」。天气 API 一般很快,但如果你本地网络到 Open-Meteo 慢,就把timeout调到 120。另外,forecast_days=7返回的数据量不小,如果模型侧对返回体长度有限制,可能会截断,导致daily数组不完整。这时候可以在工具里只返回前 3 天,或者让模型总结后再输出。

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

这一节按真实报错来。你改完配置后,大概率会碰到下面几类,逐个对照。

401 Unauthorized。最常见的原因是 API Key 没生效。检查顺序:第一,application.yml或 Cline JSON 里的 key 是不是控制台创建的那个,注意前后有没有空格;第二,如果用了环境变量${TAOTOKEN_API_KEY},确认启动时环境变量真的注入了,可以在启动日志里打印一下 key 的前缀;第三,确认 Base URL 是 https://taotoken.net/api ,如果写成了别的域名,key 自然不被识别。还有一种情况是 key 被禁用或额度用完,去控制台看一下状态。

local proxy failed。这个报错通常出现在 Cline 里,意思是本地代理层连不上目标地址。原因往往是 Base URL 带了多余路径,比如https://taotoken.net/api/v1,SDK 再拼一次/v1就变成/api/v1/v1/chat/completions,直接 404。改回 https://taotoken.net/api 即可。另一个原因是本地网络需要走系统代理,但 Cline 没读到,这时候检查系统代理设置,或者把timeout调大重试。

reading choices 相关报错。典型信息是Error reading choices或choices is null。这说明请求发出去了,但返回体结构不符合预期。常见原因是 Model ID 写错,通道侧返回了错误信息而不是正常的choices数组。去控制台确认 Model ID,并确认该模型支持 tool use。如果模型不支持 function calling,工具调用不会触发,返回体里也不会有tool_calls,看起来就像「模型没调工具」。

OAuth 相关报错。如果你用的是 Claude Code 或类似工具,可能会看到 OAuth 流程失败。这时候不要反复重试 OAuth,直接改用 API Key 方式。Claude Code 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有 Base URL、Key、Model ID 的完整写法。ClaudeCodeAnthropic 的专用入口是 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite ,按文档把三件套填全,OAuth 报错就会消失。

还有一种隐蔽的报错:工具被调用了,但返回体里daily是 null。这通常是WeatherInfo的字段名和 Open-Meteo 返回的 JSON 不一致。Open-Meteo 用的是下划线命名,比如temperature_2m_max,而 Java 字段如果写成temperature2mMax,需要加@JsonProperty映射。检查你的 DTO,确保每个字段都有正确的映射注解。这个错误不会在模型侧报得很明显,只会表现为「天气数据缺失」。

最后,如果所有配置都对,但工具就是不被调用,检查ToolCallbackProvider有没有注册成功。在启动类里加一行日志,打印weatherTools的 bean 名称,确认它被 Spring 扫描到了。MCP Server 的@Tool方法必须是 public,且参数要有@ToolParam描述,否则模型不知道参数含义,可能直接跳过调用。

6. 把通道固定下来:长期编码与 Agent 场景的收尾建议

通道切换完成、天气工具验证通过之后,建议把配置固化下来,而不是每次手动填。对于长期跑 MCP Server 的团队,最省事的做法是把 Base URL、Key、Model ID 写进统一的配置中心或环境变量模板,新成员拉下代码只需要填自己的 Key。Coding Plan 在这里的价值就体现出来了:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,它适合持续调用的 Agent 场景,不会因为临时额度问题导致工具突然不可用。

另外,MCP Server 的日志建议加上请求 ID,这样在控制台看到异常调用时,能对应到本地哪一次工具执行。天气服务这种外部 API 依赖,最好加一层缓存,比如同一个经纬度 10 分钟内不重复请求,减少通道和外部 API 的压力。这些细节不影响通道切换本身,但能让你的 MCP Server 在真实使用中更稳。

如果你还想验证其他模型在同一个 MCP Server 上的表现,直接用模型对话入口切换 Model ID 即可,Base URL 和 Key 不用变。这就是统一通道的好处:工具不动,模型随便换。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进