MCP(Model Context Protocol)正成为 AI 工具连接的标准
1. 概述
大语言模型(LLM)只有在能读取真实数据、调用真实系统时才真正有用:查询订单、阅读工单、执行查询。以前,每个 AI 应用都要为不同的工具自己编写集成方式。Model Context Protocol(MCP)应运而生,用来规范这种连接方式。
本文讲解什么是 MCP 以及为什么它越来越受关注。然后你将用 Spring Boot 编写一个小型 MCP 服务器,配置客户端来调用它,并了解什么时候不应该使用 MCP。
2. MCP 是什么?
MCP 是一个开放协议。它定义了 AI 应用如何发现和调用外部能力的方式:工具、数据和预设提示词。你可以把 MCP 想象成 AI 的“USB 接口”。任何遵循标准的设备都能插入支持该标准的主机上运行。
2.1. 三个主要角色
- Host:用户交互的应用程序,例如桌面上的 AI 助手、IDE 或内部聊天机器人。
- Client:位于 host 内部的组件。每个 client 与一个 server 保持一一对应的连接。
- Server:为 AI 提供能力的程序,例如读取订单数据库的服务器或访问文件系统的服务器。
2.2. 服务器端的三种“基础类型”
- Tools:模型可以决定调用的函数,如
findOrder或cancelOrder。 - Resources:只读数据,如文件内容或配置记录,被纳入上下文。
- Prompts:预构建的提示词模板,用户可以选择使用。
2.3. 底层协议
MCP 使用 JSON-RPC 2.0。最常见的是两种传输方式:stdio(host 启动 server 作为子进程),和 HTTP(server 远程运行)。一个工具调用看起来像这样:
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "findOrder",
"arguments": { "orderId": "ORD-1001" }
}
}
基本的工作流程包括三个步骤:
- Client 发送
initialize以交换协议版本和支持的能力。 - Client 调用
tools/list获取工具列表,包括描述和参数的 JSON Schema。 - 当模型决定使用工具时,client 发送
tools/call如上例,然后将结果返回给模型。
重要点:模型不直接调用服务器。Host 位于中间,所以 host 可以在运行敏感工具之前要求用户确认。
3. 为什么 MCP 越来越受关注?
3.1. N × M 问题
假设有 N 个 AI 应用和 M 个系统需要连接。没有通用标准,你就得编写 N × M 种集成方式。有了通用协议,每个应用只需支持 MCP 一次,每个系统只需一个 MCP 服务器。问题就变成了 N + M。
3.2. 职责分离
拥有订单系统的团队编写并运维自己的 MCP 服务器。做聊天机器人的团队只需指向那个服务器。业务逻辑、访问权限和审计日志都在正确的地方,不会被复制到每个提示词或每个应用中。
3.3. 不被绑定到单一模型提供商
每个 LLM 提供商的 function calling 都有自己的格式。MCP 位于这些格式之上。你今天写的服务器在更换模型或更换 host 时仍然可以使用。
3.4. 生态系统在不断增长
越来越多的 AI 助手、IDE 和框架支持 MCP。有多种语言的 SDK,其中 Java 有官方 SDK,Spring AI 内置集成。这是许多人认为 MCP 是工具连接标准的有力候选的原因。不过,该协议仍在不断演变,所以长期部署时需要关注规范。
4. 用 Spring Boot 编写 MCP 服务器
你将构建一个提供两个工具的服务器,用于查询和取消订单,通过 stdio 运行。
4.1. 依赖
Spring AI 提供了 MCP 服务器的 starter。导入 Spring AI 的 BOM,并使用与你的 Spring Boot 3.x 兼容的最新版本:
<properties>
<java.version>21</java.version>
<spring-ai.version><!-- Spring AI 当前版本 --></spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
</dependencies>
Starter spring-ai-starter-mcp-server 支持 stdio 传输。如果想通过 HTTP 运行服务器,Spring AI 有用于 Web MVC 和 WebFlux 的专用 starter。
注意: Spring AI 的 artifact 名称在不同的里程碑版本之间有过变化。请检查与你所选版本相应的文档。
4.2. Domain 和 Tool
public record Order(String id, String customerName, double total, String status) {}
@Service
public class OrderTools {
private final Map<String, Order> orders = new ConcurrentHashMap<>(Map.of(
"ORD-1001", new Order("ORD-1001", "Nguyen Van An", 350_000, "SHIPPING"),
"ORD-1002", new Order("ORD-1002", "Tran Thi Binh", 120_000, "PENDING")
));
@Tool(description = "按订单代码查询订单,返回客户名称、总金额和状态")
public Order findOrder(@ToolParam(description = "订单代码,如 ORD-1001") String orderId) {
Order order = orders.get(orderId);
if (order == null) {
throw new IllegalArgumentException("未找到订单 " + orderId);
}
return order;
}
@Tool(description = "取消订单。只能取消状态为 PENDING 的订单")
public String cancelOrder(@ToolParam(description = "要取消的订单代码") String orderId) {
Order order = findOrder(orderId);
if (!"PENDING".equals(order.status())) {
return "无法取消:订单状态为 " + order.status();
}
orders.put(orderId, new Order(order.id(), order.customerName(), order.total(), "CANCELLED"));
return "已取消订单 " + orderId;
}
}
两个注解发挥主要作用:
@Tool(包org.springframework.ai.tool.annotation)将方法标记为工具。description部分是模型读取以决定是否调用工具的内容。描述越清楚,模型选择越准确。@ToolParam描述每个参数。Spring AI 从参数类型和此描述生成 JSON Schema。
注意 cancelOrder 在违反业务规则时返回一条消息,而不是抛出异常。模型读取该消息并自然地向用户解释。
4.3. 将工具注册到 MCP 服务器
@SpringBootApplication
public class OrderMcpServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderMcpServerApplication.class, args);
}
@Bean
ToolCallbackProvider orderToolProvider(OrderTools orderTools) {
return MethodToolCallbackProvider.builder()
.toolObjects(orderTools)
.build();
}
}
MethodToolCallbackProvider 扫描 orderTools 中带有 @Tool 的方法。MCP 服务器的自动配置将查找所有 ToolCallbackProvider bean 并通过 tools/list 公开它们。
4.4. 配置 stdio
spring.ai.mcp.server.name=order-mcp-server
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.stdio=true
spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
logging.file.name=./logs/order-mcp-server.log
对于 stdio,stdout 是 JSON-RPC 传输通道。任何其他打印到 stdout 的内容都会破坏协议,包括横幅和日志。因此上面的配置关闭了横幅、禁用了控制台日志并将日志转移到文件。
注意: 这是初次使用 stdio 编写 MCP 服务器时最常见的错误。甚至一行调试用的
System.out.println也会导致客户端报告解析错误或断开连接。请使用 logger 将输出写到文件。
构建成 jar 文件:
./mvnw clean package -DskipTests
5. 从客户端连接
许多 host 通过包含 mcpServers 键的 JSON 文件配置服务器。文件的名称和位置取决于每个 host:
{
"mcpServers": {
"orders": {
"command": "java",
"args": ["-jar", "/opt/mcp/order-mcp-server.jar"]
}
}
}
启动时,host 像子进程一样运行命令 java -jar ...,然后发送 initialize 和 tools/list。你问:“订单 ORD-1002 还能取消吗?如果可以,请为我取消。“模型将调用 findOrder,然后是 cancelOrder。第二次调用返回的结果如下:
{"content":[{"type":"text","text":"已取消订单 ORD-1002"}],"isError":false}
通常 host 会在运行工具之前显示一个权限对话框。对于有副作用的工具(如 cancelOrder)应该保持这种行为。
如果你想让你的 Spring 应用充当 client,Spring AI 也有 MCP 客户端 starter。这个 starter 将来自外部服务器的工具转换为可与 ChatClient 使用的工具。
6. 测试工具的行为
工具只是普通的 Java 方法,所以可以测试而不需要启动 MCP:
class OrderToolsTest {
private final OrderTools tools = new OrderTools();
@Test
void shouldCancelPendingOrder() {
String result = tools.cancelOrder("ORD-1002");
assertThat(result).isEqualTo("已取消订单 ORD-1002");
assertThat(tools.findOrder("ORD-1002").status()).isEqualTo("CANCELLED");
}
@Test
void shouldRefuseToCancelShippingOrder() {
assertThat(tools.cancelOrder("ORD-1001"))
.contains("无法取消")
.contains("SHIPPING");
}
@Test
void shouldFailWhenOrderNotFound() {
assertThatThrownBy(() -> tools.findOrder("ORD-9999"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("ORD-9999");
}
}
三个测试涵盖了成功流程、业务规则和订单未找到的情况。SDK 已经处理了协议部分。你需要确保的是业务逻辑返回正确的结果,并向模型提供易于理解的消息。
要进行端到端测试,可以使用 MCP inspector 工具。它们允许列出工具并手动调用。
7. 实际应用:需要注意什么
- 按业务逻辑设计工具,而不是按数据表。
findOrder比万能的executeSql工具更好。针对性强的工具既容易控制权限,也能帮助模型做出正确选择。 - 最小化权限。 服务器应该使用只具有必需权限的账户。分离读取工具和写入工具,并考虑只在某些环境中启用写入工具。
- 警惕提示词注入。 工具返回的数据(如用户输入的注释)可能包含试图控制模型的指令。不要让一个工具的结果自动触发危险操作,必须有用户确认。
- 只使用可信服务器。 通过 stdio 运行的第三方 MCP 服务器是你机器上的一个进程,拥有你的权限。工具描述也可能被注入恶意内容。阅读源代码或仅使用来自你信任的来源。
- 远程服务器的身份验证。 通过 HTTP 运行时,将服务器放在适当的身份验证机制后面,并记录所有工具调用的审计日志。
- 保持工具数量适中。 每个工具都会消耗上下文来描述。工具太多会导致模型容易选错。
8. 什么时候不应该使用 MCP
- 小型应用,只有一个模型,只有几个函数。 框架的直接 function calling(如
@Tool直接用于ChatClient)更简单。你不需要额外的进程和协议层。 - 流程固定,不需要模型决策。 如果第 1 步总是调用 API A,第 2 步总是调用 API B,就直接写代码。不要把一个
if语句就能解决的事情交给 LLM。 - 需要极低延迟或高确定性。 每一轮模型 → 客户端 → 服务器都会增加延迟。模型的结果也不是完全确定的。
- 不可逆的操作而没有人工审查。 转账或批量删除数据不应该作为自动工具暴露。如果必须有,一定要配合确认步骤。
- 你需要一个供其他系统调用的 API。 MCP 是为 AI 设计的。在服务之间,REST、gRPC 或消息传递仍然是正确的选择。
9. 结论
MCP 解决了一个非常实际的问题:以通用方式将 AI 连接到工具和数据,而不是让每个应用自己编写方式。由于 host-client-server 模型、三个基础类型(tools/resources/prompts)和 JSON-RPC 基础,你编写的服务器可以在多个不同的 host 中重复使用。
使用 Spring AI,编写 MCP 服务器只需要几个 @Tool 注解、一个 ToolCallbackProvider bean 和正确的 stdio 配置。困难部分在别的地方:设计具体明确的工具、严格的权限控制、防范提示词注入。你还需要清醒地认识到哪些情况下普通代码仍然是更好的解决方案。