目录
- [1. 引言](#1. 引言)
- [2. MCP 核心概念速览](#2. MCP 核心概念速览)
- [2.1 Server 与 Client](#2.1 Server 与 Client)
- [2.2 Tool 与 Function Calling](#2.2 Tool 与 Function Calling)
- [2.3 传输方式](#2.3 传输方式)
- [3. 环境准备](#3. 环境准备)
- [4. 服务端实践:构建 MCP Server](#4. 服务端实践:构建 MCP Server)
- [4.1 引入依赖](#4.1 引入依赖)
- [4.2 创建工具定义](#4.2 创建工具定义)
- [4.3 启用 MCP 支持](#4.3 启用 MCP 支持)
- [4.4 Server 端配置](#4.4 Server 端配置)
- [5. 客户端实践:集成 MCP Client](#5. 客户端实践:集成 MCP Client)
- [5.1 引入依赖](#5.1 引入依赖)
- [5.2 配置 MCP Server 连接](#5.2 配置 MCP Server 连接)
- [5.3 注入工具并调用模型](#5.3 注入工具并调用模型)
- [5.4 验证调用](#5.4 验证调用)
- [6. 全流程工作原理解析](#6. 全流程工作原理解析)
- [7. 进阶实践与生产注意事项](#7. 进阶实践与生产注意事项)
- [7.1 多工具与多服务端编排](#7.1 多工具与多服务端编排)
- [7.2 错误处理与重试](#7.2 错误处理与重试)
- [7.3 安全建议](#7.3 安全建议)
- [7.4 常见问题排查](#7.4 常见问题排查)
- [8. 总结](#8. 总结)
1. 引言
随着大模型应用的快速发展,让 AI 模型安全、标准化地访问外部工具和数据源,成为工程化落地的关键难题。Model Context Protocol(MCP)正是 Anthropic 提出的开放协议,旨在统一模型与外部系统之间的交互方式------无论模型是调用本地文件、数据库,还是远程 API,都可以通过同一套协议完成。
Spring AI 从 1.0.0 版本开始,对 MCP 提供了原生支持。开发者可以用几乎相同的编程模型构建 MCP Server(暴露工具)和 MCP Client(消费工具),让 Spring 生态的大模型应用快速具备「调用外部能力」的标准化能力。
本文将带你从零开始,完整走通 Spring AI 集成 MCP 的全流程:理解核心概念、搭建 MCP Server、配置 MCP Client,最后完成一个可运行的端到端示例。
2. MCP 核心概念速览
在动手之前,先厘清 MCP 中的几个关键角色。
2.1 Server 与 Client
- MCP Server:能力的提供方。它暴露若干工具(Tools)、资源(Resources)或提示模板(Prompts),供模型或上游应用调用。
- MCP Client:能力的消费方。它连接一个或多个 MCP Server,发现并调用 Server 暴露的能力。
在 Spring AI 中,一个应用既可以作为 Server 暴露工具,也可以作为 Client 调用其他 Server;两者甚至可以同时存在于同一个应用中。
2.2 Tool 与 Function Calling
MCP 中最常用的场景是 Tool(工具)。一个 Tool 本质上就是一个「可被模型调用的函数」:
- 模型输出一个函数调用请求(包含函数名和参数 JSON);
- Client 将请求转发给对应的 MCP Server;
- Server 执行本地函数逻辑,返回结构化结果;
- Client 再把结果返回给模型,由模型组织最终回复。
这与 OpenAI 的 Function Calling 机制高度一致,但通过 MCP,工具定义和调用被抽象为跨应用、跨语言的协议。
2.3 传输方式
Spring AI MCP 支持多种传输协议:
- stdio:通过标准输入输出通信,适合本地进程间调用;
- SSE / HTTP:通过 HTTP 长连接传输,适合远程服务;
- WebFlux / WebMvc:与 Spring Web 体系集成,适合微服务架构。
本文将以最简单的 stdio 传输方式为主线,演示完整的工具注册与调用流程。
3. 环境准备
本文示例基于以下环境:
| 依赖 | 版本 |
|---|---|
| JDK | 17 及以上 |
| Maven | 3.8+ |
| Spring Boot | 3.3.5 |
| Spring AI | 1.0.0 GA |
在 pom.xml 中引入 BOM,统一管理 Spring AI 依赖版本:
xml
<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>
由于 Spring AI 的部分依赖尚未进入 Maven Central,还需要在 pom.xml 中补充仓库:
xml
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
4. 服务端实践:构建 MCP Server
本节创建一个独立的 Spring Boot 应用,作为「天气查询」MCP Server。它暴露一个 getWeather 工具,接收城市名,返回模拟的天气信息。
4.1 引入依赖
在 pom.xml 中引入 MCP Server 相关的 Starter:
xml
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
4.2 创建工具定义
通过 @Tool 注解,我们可以非常简洁地把一个普通方法暴露为 MCP Tool。创建一个天气服务类:
java
package com.example.mcpserver;
import java.util.Map;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
@Service
public class WeatherService {
@Tool(description = "根据城市名称查询当前天气,返回温度与天气状况")
public String getWeather(
@ToolParam(description = "需要查询天气的城市名称,例如北京") String city) {
// 模拟天气查询逻辑,实际项目中可替换为真实 API 调用
Map<String, String> mockData = Map.of(
"北京", "晴,25°C",
"上海", "多云,28°C",
"深圳", "阵雨,30°C"
);
return mockData.getOrDefault(city, "暂无该城市的天气数据");
}
}
4.3 启用 MCP 支持
在启动类上启用 MCP Server 能力,并在配置文件中关闭默认 Web 路径(stdio 模式下不需要 HTTP 端点):
java
package com.example.mcpserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.MethodToolCallbackProvider;
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
}
MethodToolCallbackProvider 会扫描被 @Tool 注解的方法,并自动注册为可调用的 MCP Tool。
4.4 Server 端配置
在 application.properties 中配置 MCP Server 的基本信息、协议版本和工具开关:
properties
spring.application.name=mcp-weather-server
# MCP Server 配置
spring.ai.mcp.server.name=weather-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.type=SYNC
# 工具变更通知
spring.ai.mcp.server.tool-change-notification=true
这样,一个最小可用的 MCP Server 就完成了。它会在启动后通过 stdio 协议等待客户端连接。
5. 客户端实践:集成 MCP Client
接下来创建另一个 Spring Boot 应用,作为 MCP Client。它连接上一节的天气 Server,并把天气工具注入到一个 Chat 客户端中,让大模型可以按需调用。
5.1 引入依赖
在 Client 项目的 pom.xml 中引入 MCP Client Starter 以及模型调用相关依赖:
xml
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
</dependencies>
这里以 OpenAI 兼容模型为例,你也可以替换为其他支持的模型厂商。
5.2 配置 MCP Server 连接
在 application.properties 中声明要连接哪些 MCP Server,并配置传输参数:
properties
spring.application.name=mcp-client-demo
# 连接一个名为 weather 的 MCP Server
spring.ai.mcp.client.connections.weather.transport=STDIO
spring.ai.mcp.client.connections.weather.command=java
spring.ai.mcp.client.connections.weather.args=-jar,mcp-weather-server.jar
# 模型配置(OpenAI 兼容接口示例)
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.base-url=${OPENAI_BASE_URL}
spring.ai.openai.chat.options.model=gpt-4o-mini
其中 command 和 args 指定了以 stdio 方式启动本地 MCP Server 进程的命令。若连接远程 Server,可改用 HTTP 传输:
properties
spring.ai.mcp.client.connections.remote.transport=HTTP
spring.ai.mcp.client.connections.remote.url=http://localhost:8080/mcp
5.3 注入工具并调用模型
在客户端代码中,通过 ToolCallbackProvider 拿到已连接的 MCP Server 工具,并注入到 ChatClient:
java
package com.example.mcpclient;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder,
ToolCallbackProvider toolCallbackProvider) {
this.chatClient = builder
.defaultTools(toolCallbackProvider.getToolCallbacks())
.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}
5.4 验证调用
启动客户端应用后,可以通过 HTTP 请求验证工具调用是否打通:
bash
curl "http://localhost:8081/chat?message=北京今天天气怎么样?"
预期返回类似:
text
北京今天天气晴,气温 25°C。
此时,完整的链路是:用户提问 → 大模型识别需要调用 getWeather 工具 → MCP Client 将调用请求转发给 MCP Server → Server 执行本地逻辑并返回结果 → 大模型整合结果生成自然语言回复。
6. 全流程工作原理解析
为了加深理解,下面用一张流程图总结上述调用链:
#mermaid-svg-WcN2MuOo4irfqAFn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WcN2MuOo4irfqAFn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WcN2MuOo4irfqAFn .error-icon{fill:#552222;}#mermaid-svg-WcN2MuOo4irfqAFn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WcN2MuOo4irfqAFn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WcN2MuOo4irfqAFn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn .marker.cross{stroke:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WcN2MuOo4irfqAFn p{margin:0;}#mermaid-svg-WcN2MuOo4irfqAFn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster-label text{fill:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster-label span{color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster-label span p{background-color:transparent;}#mermaid-svg-WcN2MuOo4irfqAFn .label text,#mermaid-svg-WcN2MuOo4irfqAFn span{fill:#333;color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .node rect,#mermaid-svg-WcN2MuOo4irfqAFn .node circle,#mermaid-svg-WcN2MuOo4irfqAFn .node ellipse,#mermaid-svg-WcN2MuOo4irfqAFn .node polygon,#mermaid-svg-WcN2MuOo4irfqAFn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .rough-node .label text,#mermaid-svg-WcN2MuOo4irfqAFn .node .label text,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape .label,#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape .label{text-anchor:middle;}#mermaid-svg-WcN2MuOo4irfqAFn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .rough-node .label,#mermaid-svg-WcN2MuOo4irfqAFn .node .label,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape .label,#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape .label{text-align:center;}#mermaid-svg-WcN2MuOo4irfqAFn .node.clickable{cursor:pointer;}#mermaid-svg-WcN2MuOo4irfqAFn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn .arrowheadPath{fill:#333333;}#mermaid-svg-WcN2MuOo4irfqAFn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WcN2MuOo4irfqAFn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WcN2MuOo4irfqAFn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WcN2MuOo4irfqAFn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WcN2MuOo4irfqAFn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WcN2MuOo4irfqAFn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WcN2MuOo4irfqAFn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster text{fill:#333;}#mermaid-svg-WcN2MuOo4irfqAFn .cluster span{color:#333;}#mermaid-svg-WcN2MuOo4irfqAFn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WcN2MuOo4irfqAFn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WcN2MuOo4irfqAFn rect.text{fill:none;stroke-width:0;}#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape p,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WcN2MuOo4irfqAFn .icon-shape .label rect,#mermaid-svg-WcN2MuOo4irfqAFn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WcN2MuOo4irfqAFn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WcN2MuOo4irfqAFn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WcN2MuOo4irfqAFn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不需要
需要调用工具
用户提问
ChatClient
大模型判断是否需要调用工具
直接生成回复
MCP Client
MCP Server(工具实现方)
执行本地函数逻辑
返回结构化结果
整合结果生成最终回复
返回给用户
关键点在于:
- 工具定义与实现解耦:Server 只关心「能力」本身,Client 只关心「会话编排」,协议负责两者之间的标准化通信。
- 模型决定调用时机:工具是否被调用、参数如何填充,完全由大模型根据上下文判断,开发者无需编写硬编码的分支逻辑。
- 可组合性:一个 Client 可以同时连接多个 MCP Server,例如天气 Server、数据库 Server、浏览器 Server,形成一个能力丰富的智能体。
7. 进阶实践与生产注意事项
7.1 多工具与多服务端编排
当系统需要多个能力来源时,可以在配置文件中声明多个连接,并在代码中聚合它们的工具:
java
@Configuration
public class ToolConfiguration {
@Bean
public ToolCallbackProvider allTools(
List<ToolCallbackProvider> providers) {
List<ToolCallback> callbacks = providers.stream()
.flatMap(p -> List.of(p.getToolCallbacks()).stream())
.toList();
return ToolCallbackProvider.from(callbacks);
}
}
7.2 错误处理与重试
生产环境中,远程 MCP Server 可能因网络等原因暂时不可用。建议在 Client 侧做以下处理:
- 为工具调用设置超时时间;
- 捕获调用异常时,向模型返回友好的错误信息,让模型能够向用户解释;
- 对关键工具实现幂等逻辑,避免重复调用产生副作用。
7.3 安全建议
MCP 本质上是让模型获得了「操作外部系统」的权限,因此安全边界非常重要:
- 最小权限原则:每个 Server 只暴露必要工具,避免把危险操作(如删除数据、执行命令)无差别开放;
- 输入校验:即使模型生成的参数通常是合理的,也必须对入参做严格校验,防止注入或越权访问;
- 敏感信息隔离:API Key、数据库连接串等敏感信息不要出现在工具描述或返回结果中;
- 审计日志:记录工具调用的入参、结果和调用方,便于问题追踪。
7.4 常见问题排查
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型不调用工具 | 工具描述不清晰 | 优化 @Tool 的 description,说明适用场景 |
| 连接建立失败 | 命令或传输配置错误 | 检查 stdio 的 command/args,或 HTTP 的 url |
| 工具返回空结果 | 参数名不匹配 | 确认 @ToolParam 描述与模型传参一致 |
| 依赖解析失败 | 仓库未配置 | 确认已添加 Spring Milestones/Snapshots 仓库 |
8. 总结
本文从 MCP 的核心概念出发,带你完整走通了 Spring AI 集成 MCP 的实践路径:
- 使用
@Tool注解和MethodToolCallbackProvider构建 MCP Server; - 通过
spring.ai.mcp.client.connections配置 MCP Client; - 将远程工具注入
ChatClient,实现大模型驱动的工具调用; - 了解了生产环境中的多工具编排、安全与排错要点。
MCP 的价值在于用统一协议连接「模型」与「世界」。结合 Spring AI 的深度集成,Java 开发者可以用熟悉的 Spring 编程模型,快速构建出能够操作真实数据与系统的大模型应用。希望本文能成为你上手 Spring AI + MCP 的实用起点。