Java开发者必看:用MCP协议让Claude实时查询天气,TaoToken统一Key接入实战
1. 为什么 Java 开发者需要给 Claude 接上 MCP 天气能力如果你平时用 Claude Desktop 写代码、查资料会发现它有个天然短板知识截止到训练时间问它“北京现在天气怎么样”它只能告诉你“我无法获取实时数据”。MCPModel Context Protocol就是解决这个问题的标准协议它让 AI 模型通过 JSON-RPC 调用你写的外部工具把实时数据喂回对话里。MCP 是什么简单说它是 Anthropic 开源的一套“AI 与外部工具通信规范”支持 stdio 和 SSE 两种传输方式。你写一个符合协议的 ServerClaude 就能在对话中自动发现并调用你注册的工具函数。适合谁适合手上有 Java 业务系统、想快速给 AI 助手扩展能力的后端开发者——你不需要重写技术栈用现有的 Jackson、HttpClient 就能搭起来。这篇要落地的场景很具体用 Java 写一个基于 stdio 的 MCP Server注册一个get_weather工具让 Claude Desktop 能实时查询城市天气。整个过程涉及 MCP Server 骨架、工具函数注册、Claude 侧 config 配置以及一次真实的验证请求。我试过把模拟数据和真实 API 两种方式都跑通下面按可复制的步骤展开。2. TaoToken 前置统一 Key 接入 Claude 与模型调用在动手写 MCP Server 之前先解决一个现实问题Claude Desktop 本身需要能正常调用模型而很多开发者的 Key 管理是散的——Claude 一个、其他模型一个、coding 工具又一个。TaoToken 在这里的作用是提供统一的 API Key 接入层你可以在一个控制台里管理 Key然后分别用于模型对话、Coding Plan 和 MCP 工具链的调试。具体操作路径先到官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号然后进入控制台的 API Keys 页面创建一个 Key。这个 Key 后面会用在两个地方一是 Claude Desktop 的模型接入配置二是你调试 MCP Server 时用模型对话验证工具是否被正确调用。如果你只是想让 Claude 能查天气MCP Server 本身不直接调模型它只负责响应 Claude 发来的 JSON-RPC 请求。但你需要一个能正常工作的 Claude 环境来测试整个链路。TaoToken 的模型对话入口可以用来快速验证“模型是否能理解天气查询意图”而 Coding Plan 更适合你后续把 MCP 工具扩展到代码场景时使用。注意MCP Server 的 stdio 通信是本地进程间通信不涉及网络代理你只需要保证 Java 进程能被 Claude Desktop 正常拉起即可。3. 可复制配置Java MCP Server 骨架与工具注册3.1 项目结构与 Maven 依赖先建一个最小 Maven 项目只需要 Jackson 做 JSON 处理dependencies dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency /dependencies项目结构保持扁平一个 Java 文件就够weather-mcp-server/ ├── pom.xml └── src/main/java/com/example/mcp/ └── WeatherMcpServer.java3.2 主入口与 JSON-RPC 分发MCP 基于 stdio核心逻辑是从System.in逐行读 JSON-RPC 请求处理后从System.out写响应。主循环如下public class WeatherMcpServer { private static final ObjectMapper MAPPER new ObjectMapper(); public static void main(String[] args) throws Exception { BufferedReader reader new BufferedReader(new InputStreamReader(System.in)); String line; while ((line reader.readLine()) ! null) { if (line.trim().isEmpty()) continue; try { JsonNode request MAPPER.readTree(line); handleRequest(request); } catch (Exception e) { // 生产环境建议记录日志 } } } }handleRequest根据method字段分发到initialize、tools/list、tools/call三个处理器。注意通知类请求没有id直接忽略不要回响应。private static void handleRequest(JsonNode request) { String method request.get(method).asText(); JsonNode params request.get(params); JsonNode id request.get(id); if (id null || id.isNull()) return; try { JsonNode result; switch (method) { case initialize: result handleInitialize(params); break; case tools/list: result handleToolsList(); break; case tools/call: result handleToolsCall(params); break; default: sendError(id, -32601, Method not found: method); return; } sendResponse(id, result); } catch (Exception e) { sendError(id, -32603, Internal error: e.getMessage()); } }3.3 初始化握手与工具列表initialize返回协议版本、能力声明和服务器信息。协议版本建议写0.1.0与主流客户端期望一致private static JsonNode handleInitialize(JsonNode params) { ObjectNode result MAPPER.createObjectNode(); result.put(protocolVersion, 0.1.0); ObjectNode capabilities MAPPER.createObjectNode(); capabilities.put(tools, true); result.set(capabilities, capabilities); ObjectNode serverInfo MAPPER.createObjectNode(); serverInfo.put(name, java-weather-mcp); serverInfo.put(version, 1.0.0); result.set(serverInfo, serverInfo); return result; }tools/list注册get_weather工具输入参数用 JSON Schema 描述private static JsonNode handleToolsList() { ObjectNode result MAPPER.createObjectNode(); var toolsArray MAPPER.createArrayNode(); ObjectNode tool MAPPER.createObjectNode(); tool.put(name, get_weather); tool.put(description, 获取指定城市的实时天气信息); ObjectNode inputSchema MAPPER.createObjectNode(); inputSchema.put(type, object); ObjectNode properties MAPPER.createObjectNode(); ObjectNode citySchema MAPPER.createObjectNode(); citySchema.put(type, string); citySchema.put(description, 城市名称例如北京、上海); properties.set(city, citySchema); inputSchema.set(properties, properties); inputSchema.put(required, MAPPER.createArrayNode().add(city)); tool.set(inputSchema, inputSchema); toolsArray.add(tool); result.set(tools, toolsArray); return result; }3.4 工具调用与真实天气 API 接入tools/call提取城市名调用天气查询。先用模拟数据跑通链路再替换为真实 APIprivate static JsonNode handleToolsCall(JsonNode params) { String name params.get(name).asText(); JsonNode arguments params.get(arguments); if (get_weather.equals(name)) { String city arguments.get(city).asText(); String weatherInfo getWeatherByCity(city); ObjectNode result MAPPER.createObjectNode(); ObjectNode content MAPPER.createObjectNode(); content.put(type, text); content.put(text, weatherInfo); result.set(content, MAPPER.createArrayNode().add(content)); return result; } throw new IllegalArgumentException(未知工具: name); }模拟数据版本private static String getWeatherByCity(String city) { String[] conditions {晴朗, 多云, 小雨, 阴天, 雷阵雨}; Random random new Random(); String condition conditions[random.nextInt(conditions.length)]; int temperature 5 random.nextInt(30); int humidity 40 random.nextInt(50); return String.format(%s天气%s温度%d℃湿度%d%%, city, condition, temperature, humidity); }接真实 API 时用 Java 11 的HttpClient替换即可。以和风天气为例把 API Key 放到环境变量里不要硬编码private static String getWeatherByCity(String city) throws Exception { String apiKey System.getenv(WEATHER_API_KEY); String url String.format( https://devapi.qweather.com/v7/weather/now?location%skey%s, city, apiKey ); HttpClient client HttpClient.newHttpClient(); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(url)) .GET() .build(); HttpResponseString response client.send(request, HttpResponse.BodyHandlers.ofString()); JsonNode root MAPPER.readTree(response.body()); JsonNode now root.get(now); return String.format(%s天气%s温度%s℃湿度%s%%, city, now.get(text).asText(), now.get(temp).asText(), now.get(humidity).asText()); }3.5 Claude Desktop 侧 config 配置编译打包后在 Claude Desktop 配置文件中注册 MCP Server。macOS 路径是~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 是%APPDATA%\Claude\claude_desktop_config.json。如果打成包含依赖的 fat jar{ mcpServers: { java-weather: { command: java, args: [-jar, /absolute/path/to/weather-mcp-server.jar] } } }如果直接跑 class 文件需要把 Jackson 的 jar 也加进 classpath{ mcpServers: { java-weather: { command: java, args: [ -cp, /absolute/path/to/classes:/absolute/path/to/jackson-databind-2.15.2.jar, com.example.mcp.WeatherMcpServer ] } } }配置改完后完全退出 Claude Desktop包括托盘图标再重新启动。界面上会出现一个工具图标点开能看到get_weather的说明。4. 验证请求一次真实的天气查询动作重启 Claude Desktop 后在对话里输入“北京今天天气怎么样”。Claude 会识别意图弹出工具调用确认然后你的 Java 进程被拉起收到类似这样的 JSON-RPC 请求{ jsonrpc: 2.0, id: 1, method: tools/call, params: { name: get_weather, arguments: { city: 北京 } } }你的 Server 返回{ jsonrpc: 2.0, id: 1, result: { content: [ { type: text, text: 北京天气多云温度21℃湿度67% } ] } }Claude 拿到结果后会在对话里展示“根据查询北京天气多云温度21℃湿度67%。” 如果接的是真实 API这里就是实时数据。验证成功的标志有三个Claude 界面出现工具调用提示、Java 进程被正常拉起、对话返回了天气文本。如果只看到工具图标但调用没反应往下看排查部分。5. 本篇常见错排查5.1 Claude 看不到工具图标最常见的原因是配置文件路径写错或 JSON 格式不合法。先用python -m json.tool claude_desktop_config.json校验 JSON。另外确认command里的java在系统 PATH 中Claude Desktop 启动时的环境变量可能和你终端里不一样建议写 Java 的绝对路径。5.2 工具图标出现但调用报错如果 Claude 提示“工具执行失败”先手动在终端跑一遍 Server看是否有异常输出echo {jsonrpc:2.0,id:1,method:tools/list,params:{}} | java -jar weather-mcp-server.jar正常应该返回工具列表 JSON。如果报ClassNotFoundException说明 fat jar 没打全检查maven-assembly-plugin或maven-shade-plugin配置。5.3 中文乱码stdio 通信默认编码可能不是 UTF-8。在main方法开头强制设置System.setOut(new PrintStream(System.out, true, UTF-8)); System.setIn(new FileInputStream(FileDescriptor.in));或者在启动参数里加-Dfile.encodingUTF-8。5.4 真实 API 返回空数据和风天气、OpenWeatherMap 这类 API 对城市名格式有要求有的需要城市 ID 而不是中文名。先用 curl 单独测 APIcurl https://devapi.qweather.com/v7/weather/now?location101010100keyYOUR_KEY确认返回结构后再改 Java 解析逻辑。另外注意 API Key 不要提交到 Git用环境变量注入。5.5 进程被反复拉起Claude Desktop 每次调用工具都会启动一个新进程如果你的 Server 启动慢比如加载了大量依赖会导致超时。建议把 Server 打成 fat jar 减少类加载时间或者改用 SSE 传输方式让进程常驻。6. 把 MCP 工具链接到你的日常开发流天气查询只是个引子。你完全可以在同一个 Java MCP Server 里注册更多工具查数据库、调内部 API、读文件、发消息。Claude 会根据工具描述自动选择调用哪个你只需要保证每个工具的inputSchema描述清楚。如果你后续想把 MCP 工具用在编码场景比如让 Claude 通过 MCP 查询你本地的代码索引或构建状态可以到 TaoToken 的 Coding Plan 页面看看长期编码方案的配置方式https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。调试 MCP Server 时如果想让模型快速验证工具返回模型对话入口更轻量https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。Key 的管理和轮换在控制台完成https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档里有完整的 API 说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。最后留一个实用技巧MCP Server 的日志不要往 stdout 写stdout 是 JSON-RPC 通道任何多余输出都会破坏协议。要打日志就写 stderr 或文件这是我在调试时踩过的坑。