1. 从“一问三不知”到实时天气:Solon AI 接入 MCP 的真实场景
你问大模型“杭州明天适合户外活动吗”,它大概率会回你一句“建议查询天气预报”。不是模型不聪明,而是它压根拿不到实时数据。LLM 的训练数据有截止时间,天气、股价、库存、订单状态这类每分钟都在变的信息,它天生就是盲区。
传统做法是给每个数据源单独写一套 Function Calling 适配:天气 API 写一遍、数据库写一遍、内部工单系统再写一遍。对接 10 个工具,光胶水代码就能吃掉 10 人天,而且换个模型还得重写一遍。MCP(Model Context Protocol)想解决的就是这件事——把工具调用抽象成统一协议,模型侧只认协议,不认具体实现。
这篇要做的,是用 Solon AI 3.3 起一个天气查询 MCP 服务端,再用客户端把 LLM 接上去,核心服务端代码真的只有 5 行。同时把模型调用通道统一走 TaoToken 的 API,Key 和 Base URL 一次配好,后面换模型不用改业务代码。适合正在做 AI Agent、智能客服、旅游规划类应用的 Java 开发者,JDK 1.8 就能跑。
我试过把这套链路接到一个行程规划助手上,用户说“帮我规划杭州三日游,避开雨天”,模型会自动调用天气工具拿到未来三天预报,再结合预报排行程。整个过程不需要你在 prompt 里塞任何天气数据,工具描述由 MCP 服务端声明,模型自己决定什么时候调。
下面按“环境准备 → 服务端 5 行代码 → 客户端接入 → TaoToken 通道配置 → 启动验证 → 报错排查”的顺序走一遍,每一步都能直接复制。
2. TaoToken 前置准备:统一 Key 与 API 通道配置
在写代码之前,先把模型调用通道准备好。Solon AI 的 ChatModel 需要一个兼容 OpenAI 协议的 endpoint,TaoToken 提供的就是这个统一入口。你不需要在代码里硬编码某一家厂商的地址,Base URL 固定为https://taotoken.net/api,Key 在控制台生成。
先到控制台创建 API Key:
https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_console创建完 Key 之后,建议先到模型对话页面确认一下当前可用的模型 ID,不同模型对工具调用的支持程度不一样,选一个明确支持 function calling / tool use 的:
https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_chat如果你后面要长期跑编码类 Agent,可以顺带看一下 Coding Plan,它更适合高频调用场景:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_planKey 的管理页面在这里,方便你后续轮换或吊销:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_keys接入文档在:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_doc拿到 Key 之后,先写一个application.yml,把通道配置固定下来。Solon AI 读取配置的方式和 Spring 类似,但更轻量:
solon: ai: chat: apiUrl: https://taotoken.net/api apiKey: sk-你的TaoTokenKey model: 你的模型ID这里有个容易踩的坑:apiUrl不要写成https://taotoken.net/api/v1,Solon AI 的 OpenAI 适配层会自己拼/v1/chat/completions,你多写一层就会 404。实测下来,Base URL 保持https://taotoken.net/api最稳。
如果你用的是环境变量方式,也可以这样:
export TAOTOKEN_API_KEY=sk-你的Key export TAOTOKEN_BASE_URL=https://taotoken.net/api然后在配置里引用${TAOTOKEN_API_KEY}。生产环境建议走环境变量,别把 Key 提交到仓库。
3. 可复制配置:5 行服务端代码 + MCP 声明片段
服务端是整个链路的核心。Solon AI 通过@McpServerEndpoint和@ToolMapping两个注解,把普通 Java 方法暴露成 MCP 工具。先看依赖,pom.xml里加这几项:
<dependency> <groupId>org.noear</groupId> <artifactId>solon-ai-mcp</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>org.noear</groupId> <artifactId>solon-ai-chat</artifactId> <version>3.3.0</version> </dependency>服务端代码,核心确实只有 5 行:
@McpServerEndpoint(name = "weather-server", sseEndpoint = "/mcp/weather") public class WeatherService { @ToolMapping(description = "获取指定城市的未来三天天气预报") public String getWeather(@Param(description = "城市名称") String city) { return WeatherApi.getForecast(city); } }逐行拆一下。@McpServerEndpoint声明这是一个 MCP 服务端,name是服务标识,sseEndpoint是客户端连接的路径,这里走 SSE 传输。@ToolMapping把方法注册成工具,description会直接进入模型的工具描述,写得越清楚模型越知道什么时候调。@Param描述参数含义,同样会传给模型。
WeatherApi.getForecast(city)是你自己的实现,可以调高德、和风或者内部气象服务。这里给一个最小可运行的 mock 实现,方便你先跑通链路:
public class WeatherApi { public static String getForecast(String city) { return city + "未来三天:明天小雨 18-24℃,后天多云 19-26℃,大后天晴 20-28℃"; } }启动类:
public class App { public static void main(String[] args) { Solon.start(App.class, args); } }客户端侧,用McpClientProvider连接服务端,再挂到 ChatModel 上:
McpClientProvider toolProvider = McpClientProvider.builder() .apiUrl("http://localhost:8080/mcp/weather") .build(); ChatResponse response = chatModel.prompt("杭州明天适合户外活动吗?") .options(o -> o.toolsAdd(toolProvider)) .call(); System.out.println(response.getMessage().getContent());toolsAdd把 MCP 工具注入到本次对话,模型在推理时会看到getWeather这个工具及其描述,判断需要天气数据时自动发起调用。你不需要在 prompt 里写“请调用天气工具”,工具描述本身就是给模型看的。
如果你用 Cline MCP 或 Claude Code 这类客户端,配置格式不一样,但三件套是一样的:Base URL、Key、Model ID。以 Cline 的 MCP 配置为例:
{ "mcpServers": { "weather": { "url": "http://localhost:8080/mcp/weather", "transport": "sse" } } }模型通道那边,Cline 里填的 Base URL 是https://taotoken.net/api,Key 用你的 TaoToken Key,Model ID 填你在模型对话页确认过的那个。三件套缺一不可,少一个就是 401 或 model not found。
4. 验证请求:本地启动与成功结果对照
配置写完,先启动服务端。用 Maven 直接跑:
mvn clean compile exec:java -Dexec.mainClass="com.example.App"看到控制台输出Solon started并且监听 8080 端口,说明服务端起来了。这时候可以先用 curl 探一下 SSE 端点是否可达:
curl -N http://localhost:8080/mcp/weather正常会保持连接并等待事件推送,如果立刻返回 404,说明sseEndpoint路径写错了,检查注解里的/mcp/weather和客户端apiUrl是否一致。
然后跑客户端。第一次调用时,模型会先返回一个工具调用请求,MCP 客户端执行getWeather("杭州"),把结果回传给模型,模型再生成最终回答。成功时控制台输出类似:
杭州明天有小雨,气温 18-24℃,不太适合户外活动,建议安排室内行程。注意看日志里有没有tool_call相关的记录。Solon AI 默认会打印工具调用链路,你能看到模型请求了哪个工具、参数是什么、返回了什么。如果只看到模型直接回答“建议查询天气”,说明工具没挂上,检查toolsAdd是否真的执行了。
再验证一个多轮场景,确认会话上下文能保持:
chatModel.prompt("那后天呢?") .options(o -> o.toolsAdd(toolProvider)) .call();模型应该能结合上一轮的“杭州”上下文,直接调getWeather("杭州")拿后天数据。如果它反问“你指的是哪个城市”,说明会话 ID 没保持住,需要在客户端侧开启会话恢复。
TaoToken 通道这边,验证方式是看请求是否正常返回。如果模型调用成功,说明 Base URL 和 Key 都对。你也可以单独用 curl 测一下通道:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer sk-你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"你的模型ID","messages":[{"role":"user","content":"hi"}]}'返回正常 JSON 就说明通道没问题,问题只可能在 MCP 侧。
5. 本篇常见错排查:401、local proxy failed 与 choices 读取失败
第一个高频错误是 401。报错长这样:
{"error":{"message":"Invalid API key","type":"invalid_request_error"}}原因通常是 Key 没配、配错,或者环境变量没生效。检查application.yml里的apiKey是否以sk-开头,环境变量方式的话确认echo $TAOTOKEN_API_KEY有值。还有一种情况是 Key 被吊销了,去 API Keys 页面重新生成一个。
第二个是local proxy failed。这个报错一般出现在客户端连 MCP 服务端时,SSE 连接建立失败。常见原因有三个:服务端没启动、端口不对、路径不对。先用curl -N http://localhost:8080/mcp/weather确认端点可达,再检查客户端apiUrl是否带了多余斜杠。如果服务端在容器里,注意localhost要换成容器网络里的服务名。
第三个是reading choices相关报错,典型信息:
java.lang.NullPointerException: Cannot read the array length because "choices" is null这说明模型返回的 JSON 里没有choices字段,通常是 Base URL 拼错了。比如你写成了https://taotoken.net/api/v1,适配层又拼了一次/v1/chat/completions,变成/api/v1/v1/chat/completions,服务端返回 404 的 HTML,解析时自然拿不到choices。把 Base URL 改回https://taotoken.net/api即可。
第四个是 OAuth 相关报错。如果你用的是 Claude Code 或某些需要 OAuth 的客户端,可能会看到OAuth token expired或invalid_grant。这类客户端如果支持自定义 Base URL,直接填 TaoToken 的地址和 Key 就行,不需要走 OAuth 流程。Claude Code 的接入方式参考文档:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_claudecode第五个是工具没被调用。模型直接回答而不调工具,检查三点:@ToolMapping的description是否足够清晰、模型是否支持 tool use、toolsAdd是否真的挂上了。有些模型对工具调用支持较弱,换一个明确支持的模型 ID 再试。
第六个是 SSE 连接频繁断开。Solon AI 的 SSE 端点默认有超时,长时间无事件会断。客户端侧需要实现重连,或者在服务端配置心跳。生产环境建议加心跳事件,保持连接活跃。
6. 语义一致 CTA:把天气查询扩展成你的第一个 MCP 工具
天气查询只是最小示例,真正有价值的是这套模式可以复制到任何数据源。订单查询、库存状态、内部文档检索、GitLab issue 列表,只要你能写成 Java 方法,就能通过@ToolMapping暴露给模型。服务端 5 行代码的结构不变,变的只是方法体里的实现。
模型通道这边,TaoToken 的 Base URL 和 Key 配一次,后面换模型只改model字段,业务代码不动。如果你要跑多个 MCP 服务端,客户端侧可以挂多个McpClientProvider,模型会自动在多个工具之间选择。
下一步建议你先把自己业务里最高频的那个查询接口改造成 MCP 工具,跑通“模型自动调用 → 拿到真实数据 → 生成回答”的完整链路。接入文档和 API Keys 都在下面,配置过程中遇到 401 或 choices 读取失败,对照第 5 节排查即可。
接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_doc_end API Keys:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=mcp_weather_keys_end服务端代码跑起来之后,你会发现真正的门槛不在 MCP 协议本身,而在于工具描述怎么写才能让模型准确判断调用时机。这个只能靠实测调,多跑几轮对话,看模型在什么情况下会调、什么情况下不调,慢慢把description磨到刚好。