一、MCP是什么
MCP 是 Anthropic 2024 年底提出的开放协议,解决的问题是:AI 应用要接入外部工具和数据源时,每接一个就要写一套适配代码,碎片化严重。
核心概念
- MCP Server:对外暴露能力的服务端,封装了具体的工具、资源、提示词模板
- MCP Client:大模型应用(如 Claude Desktop、Cursor、各种 Agent 框架),通过协议连接 Server
三种原语
- Tools:可执行的操作(等价于 Function Call)
- Resources:可读取的数据上下文(文件、数据库记录等)
- Prompts:可复用的提示词模板
传输层
支持 stdio(本地进程)和 HTTP/SSE(远程服务)
二、MCP 做的事
定义一套标准化的客户端 - 服务端通信协议,让任何支持 MCP 的 AI 客户端(Claude Desktop、Cursor、豆包等)都能即插即用地连接任何 MCP Server(文件系统、数据库、GitHub、飞书、Postgres……),不用为每个工具单独写集成。
三、类比
MCP 之于 AI Agent,就像 USB 之于外设 ,统一接口,热插拔。
四、MCP 的典型应用形态
飞书生态:lark-doc、lark-sheets、lark-calendar、lark-im、lark-mail、lark-wiki 等, 这些本质上是把飞书 OpenAPI 封装成一组 Tool 暴露给模型,用户授权后即可读写飞书文档、发消息、建日程。这正是 MCP 的典型应用形态:一个 MCP Server 对接飞书,暴露一批 Tool 给任意 AI 客户端。
我的理解是:
- MCP是一个程序(MCP Server),定义了一套API给第三方调用(MCP Client)。
- MCP Server 通过@Tool封装调用内部系统接口
- 有点类似于JDBC
五、具体实例
5.1 需求描述
现在新开发一个AI Agent 系统,需要对接公司内部已经上线运行的系统,要求不能修改公司内部系统的源码,但可以通过HTTP+授权token访问接口,怎么做?
5.2 分析
这是典型的无侵入式对接场景,内部系统是 "黑盒",你只能在外面包一层。核心思路是:建一个独立的 MCP Server 适配层,把内部系统的 HTTP API 封装成标准化的 MCP Tools,权限、限流、审计全部收敛在这一层。
5.3 整体架构

关键点:内部系统一行代码不改,所有适配逻辑都在你新建的 MCP Server 里。
5.4 权限处理设计(核心难点)
不能改内部系统,但内部系统有权限控制。你需要在 MCP Server 适配层解决"谁能调什么"和 "调用时用什么身份 "两个问题。
两种权限模型

重点说明一下模型B
Agent 只拿到 "当前用户的身份凭据",权限判定永远由内部系统自己执行。Agent / 适配层是 "信使",不是 "裁判"。权限规则和数据不出内部系统域,Agent 侧只是透传身份、按需做工具可见性裁剪。
情况 A:共用同一身份源(最常见、最简单),Agent 应用和内部系统都对接公司同一个 SSO(单点登录)/LDAP。
情况 B:身份源不同(需要账号关联),
Agent 用户ID ──► internal_user_mapping 表 ──► 内部系统用户ID
5.5 MCP 工具层设计(@Tool)
这是最关键的一层 ,把内部系统的能力有选择地暴露为 MCP 工具。不是所有内部 API 都要暴露,要做工具粒度的设计。

5.5 完整调用链路

六、样例代码
6.1 本地 Tools(Function Calling)
这是最基础的形态,工具定义在本地,直接通过 @Tool 注解注册给 ChatClient。
1、定义工具类
package com.example.localtools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
@Service
public class WeatherTools {
/**
* 【概念解释】
* @Tool 注解告诉 Spring AI:这个方法是一个可被大模型调用的工具。
* - description 会被发送给大模型,帮助它理解"什么时候该调用这个工具"
* - 方法参数会自动生成 JSON Schema,作为 Function Calling 的 inputSchema
* - 方法返回值会作为 tool execution result 回传给大模型继续推理
*
* 这就是 Function Calling 的完整闭环:模型决定调用 → Spring AI 执行 → 结果回传模型
*/
@Tool(description = "根据城市名称获取当前天气信息")
public String getWeather(
@ToolParam(description = "城市名称,如 Beijing、Shanghai") String city) {
// 模拟调用天气 API
return switch (city) {
case "Beijing", "北京" -> "北京:28°C,晴,空气质量优";
case "Shanghai", "上海" -> "上海:32°C,多云,有阵雨";
case "Guangzhou", "广州" -> "广州:35°C,雷阵雨";
default -> "暂无 " + city + " 的天气数据";
};
}
@Tool(description = "根据城市和日期获取未来天气预报")
public String getForecast(
@ToolParam(description = "城市名称") String city,
@ToolParam(description = "日期,格式 YYYY-MM-DD") String date) {
return city + " 在 " + date + " 的预报:30°C,多云转晴";
}
}
2、使用工具
package com.example.localtools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class WeatherAssistant {
private final ChatClient chatClient;
public WeatherAssistant(ChatClient.Builder builder, WeatherTools weatherTools) {
this.chatClient = builder
// 【关键】把工具实例注册给 ChatClient
// Spring AI 会扫描 @Tool 注解,生成 Function Calling 所需的工具定义
.defaultTools(weatherTools)
.build();
}
public String ask(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
3、运行效果
// 输入:"北京今天天气怎么样?适合出门吗?"
// 内部流程:
// 1. 大模型判断需要调用 getWeather 工具,生成参数 {"city": "Beijing"}
// 2. Spring AI 拦截 tool call,执行 WeatherTools.getWeather("Beijing")
// 3. 返回 "北京:28°C,晴,空气质量优" 给大模型
// 4. 大模型基于工具结果生成最终回答:"北京今天28°C晴天,空气质量优,非常适合出门!"
这就是 Tools/Function Calling:模型在推理过程中决定调用外部函数,框架负责执行并回传结果。
6.2 MCP Server把工具暴露为标准协议服务
现在把同样的天气工具包装成 MCP Server。关键区别:工具不再直接绑定某个 AI 应用,而是通过 MCP 协议对外暴露,任何 MCP 客户端(Claude Desktop、Cursor、另一个 Spring AI 应用)都能连接使用。
1、依赖
<!-- mcp-server/pom.xml -->
<dependencies>
<!-- MCP Server 启动器(STDIO 模式,适合本地/桌面工具) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
<!-- 如果需要 SSE/HTTP 远程访问,用这个:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
-->
</dependencies>
2、工具类
package com.example.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(description = "根据城市名称获取当前天气信息")
public String getWeather(
@ToolParam(description = "城市名称") String city) {
return switch (city) {
case "Beijing", "北京" -> "北京:28°C,晴,空气质量优";
case "Shanghai", "上海" -> "上海:32°C,多云,有阵雨";
default -> "暂无 " + city + " 的天气数据";
};
}
@Tool(description = "获取城市未来天气预报")
public String getForecast(
@ToolParam(description = "城市名称") String city,
@ToolParam(description = "日期 YYYY-MM-DD") String date) {
return city + " " + date + ":30°C,多云转晴";
}
}
3、注册为 MCP 工具
package com.example.mcpserver;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
/**
* 【MCP 核心概念】
* 把 WeatherService 里的 @Tool 方法包装成 ToolCallbackProvider,
* MCP Server 自动配置会检测到这个 Bean,把其中的工具转换为 MCP Tool 规范,
* 通过 STDIO/SSE 协议对外暴露。
*
* 注意:这里的工具定义和 Part 1 完全一样,
* 区别只在于"谁来消费"——本地 Tools 由当前应用的 ChatClient 直接消费,
* MCP Server 则通过标准协议让任何客户端都能消费。
*/
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
}
4、MCP Server 配置
# application.yml
spring:
ai:
mcp:
server:
name: weather-mcp-server # 服务名,客户端连接时会看到
version: 1.0.0
type: SYNC # SYNC 同步 / ASYNC 响应式
instructions: "本服务器提供天气查询工具,支持当前天气和未来预报"
capabilities:
tool: true # 暴露工具能力
resource: false # 不暴露资源(文件/数据)
prompt: false # 不暴露提示词模板
5、MCP Server 还能暴露什么(不止 Tools)
MCP 协议定义了三种原语,Tools 只是其中之一
package com.example.mcpserver;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.ai.mcp.server.McpServerFeatures;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Map;
@Configuration
public class McpFeaturesConfig {
/**
* 【MCP 原语 1: Resources】
* 暴露可读取的数据上下文(文件、数据库记录、API 数据等)
* 客户端可以像"读文件"一样读取这些资源,不需要 Function Calling
*/
@Bean
public List<McpServerFeatures.SyncResourceSpecification> systemInfoResource() {
var resource = McpSchema.Resource.builder()
.uri("weather://system/info")
.name("system-info")
.description("天气服务器的系统信息")
.mimeType("application/json")
.build();
var spec = new McpServerFeatures.SyncResourceSpecification(resource,
(exchange, request) -> {
var info = Map.of("version", "1.0.0", "status", "running");
return McpSchema.ReadResourceResult.builder()
.contents(List.of(
McpSchema.TextResourceContents.builder()
.uri(request.uri())
.text(info.toString())
.mimeType("application/json")
.build()))
.build();
});
return List.of(spec);
}
/**
* 【MCP 原语 2: Prompts】
* 暴露可复用的提示词模板,客户端可以直接拉取使用
*/
@Bean
public List<McpServerFeatures.SyncPromptSpecification> weatherPrompts() {
var prompt = McpSchema.Prompt.builder()
.name("weather-summary")
.description("生成天气摘要的提示词模板")
.arguments(List.of(
McpSchema.PromptArgument.builder()
.name("city")
.description("城市名称")
.required(true)
.build()))
.build();
var spec = new McpServerFeatures.SyncPromptSpecification(prompt,
(exchange, request) -> {
String city = (String) request.arguments().get("city");
var msg = McpSchema.PromptMessage.builder()
.role(McpSchema.Role.USER)
.content(McpSchema.TextContent.builder()
.text("请用简洁的语言总结 " + city + " 的天气情况,并给出穿衣建议。")
.build())
.build();
return McpSchema.GetPromptResult.builder()
.messages(List.of(msg))
.description("城市天气摘要提示词")
.build();
});
return List.of(spec);
}
// 【MCP 原语 3: Tools】就是上面 WeatherService 里 @Tool 注解的方法
// 由 ToolCallbackProvider 自动转换,不需要手动写 Specification
}
这就是 MCP 比单纯 Function Calling 更丰富的地方:它不仅标准化了工具调用,还定义了资源读取、提示词模板、日志通知、进度通知等一整套协议。
6.3 MCP Client连接远程 MCP Server 并使用工具
现在写一个客户端应用,连接上面的 MCP Server,让大模型通过 MCP 协议调用远程工具。
依赖
<!-- mcp-client/pom.xml -->
<dependencies>
<!-- MCP Client 启动器 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<!-- 大模型 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
</dependencies>
2、配置 MCP 连接
# application.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
mcp:
client:
enabled: true
name: weather-mcp-client
version: 1.0.0
type: SYNC
request-timeout: 30s
# 【关键】配置要连接的 MCP Server
# 这里用 STDIO 方式(启动一个子进程运行 Server)
stdio:
connections:
weather-server:
command: java
args:
- "-jar"
- "../mcp-server/target/mcp-server-1.0.0.jar"
# 如果 MCP Server 是远程 SSE 服务,用这个:
# sse:
# connections:
# weather-server:
# url: http://localhost:8080/sse
3、使用 MCP 工具
package com.example.mcpclient;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.client.sync.SyncMcpToolCallbackProvider;
import org.springframework.stereotype.Service;
@Service
public class WeatherChatService {
private final ChatClient chatClient;
/**
* 【MCP Client 核心概念】
* SyncMcpToolCallbackProvider 是 Spring AI 自动配置的 Bean,
* 它会:
* 1. 连接所有配置的 MCP Server
* 2. 通过 MCP 协议的 tools/list 发现远程工具
* 3. 把远程工具包装成本地的 ToolCallback
* 4. 当大模型发起 Function Calling 时,通过 MCP 协议转发到远程 Server 执行
*
* 【与本地 Tools 的对比】
* - 本地 Tools:ChatClient 直接调用 JVM 内的方法
* - MCP Tools:ChatClient 调用 ToolCallback → MCP 协议序列化 → 网络/STDIO 传输 → 远程 Server 执行 → 结果返回
*
* 但对大模型来说,两者完全一样——都是 Function Calling,
* 模型不需要知道工具是本地的还是远程的。
*/
public WeatherChatService(ChatClient.Builder builder,
SyncMcpToolCallbackProvider mcpToolCallbackProvider) {
this.chatClient = builder
// 把 MCP 发现的所有远程工具注册给 ChatClient
.defaultToolCallbacks(mcpToolCallbackProvider.getToolCallbacks())
.build();
}
public String chat(String question) {
return chatClient.prompt()
.user(question)
.call()
.content();
}
}
4、启动类和测试接口
package com.example.mcpclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class McpClientApplication {
private final WeatherChatService weatherChatService;
public McpClientApplication(WeatherChatService weatherChatService) {
this.weatherChatService = weatherChatService;
}
public static void main(String[] args) {
SpringApplication.run(McpClientApplication.class, args);
}
@GetMapping("/chat")
public String chat(@RequestParam String q) {
return weatherChatService.chat(q);
}
}
6.4 完整调用链路对比
本地 Tools 调用链
用户提问
→ ChatClient 发送给大模型(携带本地工具定义)
→ 大模型返回 tool_call(函数名 + 参数)
→ Spring AI 直接调用 JVM 内的 WeatherTools.getWeather()
→ 结果作为 tool message 回传大模型
→ 大模型生成最终回答
MCP 工具调用链
用户提问
→ ChatClient 发送给大模型(携带 MCP 发现的工具定义)
→ 大模型返回 tool_call(函数名 + 参数)
→ Spring AI 调用 MCP ToolCallback
→ MCP Client 通过协议发送 tools/call 请求(JSON-RPC over STDIO/SSE)
→ MCP Server 收到请求,执行 WeatherService.getWeather()
→ 结果通过协议返回 MCP Client
→ 结果作为 tool message 回传大模型
→ 大模型生成最终回答
注意看:从 "大模型返回 tool_call" 之后,本地和 MCP 的区别只是执行位置和传输方式不同。对大模型而言,两者都是标准的 Function Calling。
6.5 核心关系总结
┌─────────────────────────────────────────────────────┐
│ 大模型 (LLM) │
│ 看到的都是 Function Calling 工具定义 │
└──────────────┬──────────────────────┬───────────────┘
│ │
┌──────────▼─────────┐ ┌─────────▼──────────────┐
│ 本地 ToolCallback │ │ MCP ToolCallback │
│ (直接调 JVM 方法) │ │ (通过 MCP 协议转发) │
└──────────┬─────────┘ └─────────┬──────────────┘
│ │
┌──────────▼─────────┐ ┌─────────▼──────────────┐
│ WeatherTools │ │ MCP Protocol │
│ (当前应用内) │ │ (STDIO / SSE / HTTP) │
└────────────────────┘ └─────────┬──────────────┘
│
┌─────────▼──────────────┐
│ MCP Server │
│ → WeatherService │
│ → Resources │
│ → Prompts │
└────────────────────────┘