1. 为什么要在 SpringBoot 里折腾 MCP 天气工具调用
如果你已经用 LangChain4j 接过 Ollama,大概率体验过 Function Calling 的爽点:模型自己判断该不该调工具、该传什么参数。但真到项目里,工具一多就乱——每个工具都要在客户端硬编码注册,模型换了、工具改了,代码跟着改一遍。MCP(Model Context Protocol)想解决的就是这件事:把工具从客户端里抽出来,变成一个独立的 Server,通过标准协议暴露 Tools、Resources、Prompts,客户端只管连上去、发现工具、发起调用。
这篇要落地的场景很具体:SpringBoot 做宿主,LangChain4j 做编排,Ollama 跑本地模型,MCP Server 暴露一个getWeather天气工具,最后用一句「青岛天气?」跑通整条调用链。适合谁?已经会写 SpringBoot、装过 Ollama、想从「单机 Function Calling」升级到「协议化工具调用」的开发者。读完你能拿到可复制的 Maven 依赖、application.yml、MCP 工具注册骨架,以及一次真实的调用链验证动作。
有个前提得先说清楚:模型必须支持原生 Function Calling。qwen2.5:7b-instruct、llama3.1:8b-instruct这类可以,纯对话模型不行。另外 Ollama 建议走 OpenAI 兼容端点/v1调用,函数调用的稳定性会明显好于原生/api/chat。这两点决定了后面配置怎么写。
2. TaoToken 前置:把模型接入这步先理顺
本地 Ollama 适合调试,但一旦你要换更强的模型、或者团队里几个人共用一套模型服务,本地跑就不太够了。这时候可以用 TaoToken 做统一的模型接入层,它兼容 OpenAI 协议,LangChain4j 的OpenAiChatModel直接改baseUrl和apiKey就能切过去,不用动业务代码。
具体操作:先到 TaoToken 控制台 创建一个 API Key,然后在 API Keys 管理页 复制出来。接入地址用https://taotoken.net/api,注意这个地址不带任何查询参数,直接填进baseUrl即可。
如果你只是想先验证模型能不能正确返回tool_calls,不想写代码,可以直接在 模型对话 里手动发一句带工具定义的请求,看返回结构。这一步能帮你快速排除「模型不支持工具调用」这个最常见的坑。
注意:TaoToken 在这里的角色是模型接入层,不是替代你的编辑器或 IDE。它负责把请求转发到合适的模型,工具注册、MCP 协议交互这些还是在你自己的 SpringBoot 工程里完成。
对于长期要跑编码 Agent、或者需要稳定模型供给的场景,可以了解下 Coding Plan,它更适合持续性的开发任务。接入细节可以对照 接入文档 一步步来。
3. 可复制配置:MCP Server 与 Client 双端骨架
整个链路分两个工程:MCP Server(暴露天气工具,端口 8081)和 MCP Client(SpringBoot + LangChain4j,端口 8082)。先搭 Server。
3.1 MCP Server 依赖与配置
Server 端用 Spring AI 的 MCP Starter,它能自动把@Tool注解的方法暴露成 MCP 工具。pom.xml 核心部分:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> </parent> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>1.1.4</version> </dependency> </dependencies> <repositories> <repository> <id>spring-milestones</id> <url>https://repo.spring.io/milestone</url> </repository> </repositories>application.yml 里指定协议为 SSE,并给服务器起个名字:
server: port: 8081 spring: ai: mcp: server: protocol: SSE name: weather-mcp-server version: 1.0.03.2 定义并注册天气工具
工具方法用@Tool和@ToolParam标注,description 是模型理解工具的唯一途径,写得越清楚,调用成功率越高:
package com.badao.ai.mcpserver; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Service; @Service public class WeatherService { @Tool(name = "getWeather", description = "查询指定城市的天气信息") public WeatherResult getWeather( @ToolParam(description = "城市名称,例如:北京") String city) { System.out.println("调用了getWeather, city=" + city); return new WeatherResult(city, "晴", "25°C", "湿度:60%"); } public record WeatherResult(String city, String weather, String temperature, String details) {} }然后在启动类里注册成ToolCallbackProvider:
@Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); }3.3 MCP Client 依赖与模型配置
Client 端用 LangChain4j 的 MCP 模块。pom.xml 关键依赖:
<properties> <java.version>17</java.version> <langchain4j.version>1.0.0-beta3</langchain4j.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-mcp</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>${langchain4j.version}</version> </dependency> </dependencies>application.yml 里配置 Ollama,注意超时时间必须加,否则本地模型响应慢会直接断掉:
server: port: 8082 langchain4j: ollama: chat-model: base-url: http://localhost:11434 model-name: qwen2.5:7b-instruct log-requests: true log-responses: true timeout: 60s3.4 手动组装 AiService 与 MCP 客户端
这里不用@AiService自动扫描,改成手动配置,避免版本冲突导致的 Bean 找不到问题:
@Configuration public class ManualMcpConfig { @Bean public WeatherAssistant weatherAssistant() { ChatLanguageModel model = OpenAiChatModel.builder() .baseUrl("http://localhost:11434/v1") .apiKey("ollama") .modelName("qwen2.5:7b-instruct") .timeout(Duration.ofSeconds(60)) .build(); McpTransport transport = new HttpMcpTransport.Builder() .sseUrl("http://localhost:8081/sse") .logRequests(true) .logResponses(true) .build(); McpClient mcpClient = new DefaultMcpClient.Builder() .transport(transport) .build(); ToolProvider toolProvider = McpToolProvider.builder() .mcpClients(List.of(mcpClient)) .build(); return AiServices.builder(WeatherAssistant.class) .chatLanguageModel(model) .toolProvider(toolProvider) .build(); } }接口和控制器很简单:
public interface WeatherAssistant { String chat(String userMessage); } @RestController public class ChatController { private final WeatherAssistant weatherAssistant; public ChatController(WeatherAssistant weatherAssistant) { this.weatherAssistant = weatherAssistant; } @PostMapping("/chat") public Map<String, String> chat(@RequestBody Map<String, String> request) { String response = weatherAssistant.chat(request.get("message")); return Map.of("response", response); } }4. 验证请求:一次天气查询的完整调用链
启动顺序很重要:先起 MCP Server(8081),再确认 Ollama 在跑,最后起 Client(8082)。
先验证 Server 的 SSE 端点是否正常:
curl http://localhost:8081/sse如果返回event:endpoint和sessionId,说明 Server 起来了,且是旧版 SSE 协议。
再确认 Ollama 模型支持工具调用:
curl http://localhost:11434/api/chat -d '{ "model": "qwen2.5:7b-instruct", "messages": [{"role": "user", "content": "青岛天气如何?"}], "tools": [{ "type": "function", "function": { "name": "getWeather", "description": "获取城市天气", "parameters": { "type": "object", "properties": {"city": {"type": "string", "description": "城市名"}}, "required": ["city"] } } }], "stream": false }'返回里带tool_calls字段就说明模型支持。如果content是普通文本、没有tool_calls,换模型。
最后打 Client 接口:
curl -X POST http://localhost:8082/chat \ -H "Content-Type: application/json" \ -d '{"message": "青岛天气?"}'预期结果:Client 返回类似「青岛今天晴,25°C,湿度 60%」的文本,同时 MCP Server 控制台打印出调用了getWeather, city=青岛。看到这行日志,说明整条链路——模型判断 → MCP 工具发现 → tools/call → 结果回填 → 模型生成回答——全部跑通了。
5. 本篇常见错排查
启动报 IllegalConfigurationException,提示找不到 ChatLanguageModel Bean。这是@AiService自动扫描没生效。解决办法就是本篇用的手动配置类,显式AiServices.builder()组装,不依赖自动扫描。
调用接口返回的是模拟文本,没有真正调工具。两个原因:模型不支持 Function Calling,或者没走/v1端点。用上面的 curl 测一下模型是否返回tool_calls,然后把baseUrl改成http://localhost:11434/v1。
Unexpected status code: 404。SSE 会话失效了,客户端还在往旧端点发请求。重启 Client,或者考虑切到 Streamable HTTP 传输模式。
SSE 连接超时。这是 SSE 空闲关闭的正常现象,不影响功能,忽略警告即可。如果频繁出现,说明该换 Streamable HTTP 了。
ClassNotFoundException 或版本冲突。最常见的是混用了不同 beta 版本的 LangChain4j 模块,比如 mcp 用 1.1.0-beta7、ollama 用 1.0.0-beta3,内部 API 不兼容。统一所有模块版本到1.0.0-beta3,并且直接引langchain4j-mcp和langchain4j-open-ai,别引多余的 Starter。
模型响应慢导致超时。本地 7B 模型首次加载慢,timeout设成 60s 以上,别用默认值。
6. 接入与排障的下一步
如果你在接入过程中卡在模型调用这一层,比如不确定tool_calls返回结构对不对,可以直接在 模型对话 里手动发请求验证,比写代码快。需要换模型或统一管理 Key 的时候,去 API Keys 创建,接入地址固定用https://taotoken.net/api。协议细节和参数说明对照 接入文档 查,比翻源码省事。
跑通天气这个例子之后,你可以把WeatherService换成真实天气 API,再往 Server 里加第二个、第三个工具,观察模型是怎么在多个工具之间做选择的。这一步比任何教程都更能帮你理解 MCP 的价值——工具和客户端解耦之后,扩展成本几乎为零。