目录
- 一、MCP概述
- 二、MCP核心概念
- 三、LangChain4j MCP客户端
- 四、MCP与AI Service集成
- 五、MCP Server
- 六、MCP与Function Calling对比
- 七、注意事项
MCP 官方文档:MCP Introduction
LangChain官网:LangChain4j,官方英文文档:Get Started | LangChain4j,API 文档:Overview (LangChain4j)
非官方中文文档:快速开始 | LangChain4j 中文文档
官方示例代码仓库:langchain4j/langchain4j-examples
LangChain4j版本:1.14.0-beta24
一、MCP概述
1.1、解决什么问题
在 LangChain4j 中通过 @Tool 和 ToolProvider 可以实现函数调用,大模型能主动调 Java 方法去查数据库、调API、做计算。功能是实现了,但有个问题一直在那里:工具跟项目是强绑定的。
在项目 A 里写了一套 OrderTools、PaymentTools,等到项目 B、项目 C 也需要同样的能力时,是应该把代码拷过去再改一遍,还是造个 REST API 让它们调?更尴尬的是,隔壁组的 Python 项目也想用这些工具,那时候就真没辙了——人家总不能用写好的 Java 代码吧?
所以 Function Calling 也有不少痛点:
- 工具与应用强耦合:每个项目必须独自维护工具代码,重复造轮子
- 跨语言不可复用:Java 写的工具,Python/Node.js 用不了,反之亦然
- 没有统一的发现机制:每个 AI 框架(LangChain、LangChain4j、Semantic Kernel……)各搞一套,互不兼容
- 集成成本是 M×N:有 M 个 AI 应用、N 个数据源/工具,得做 M×N 次集成
MCP(Model Context Protocol,模型上下文协议)就是为了解决这个问题而生的。它是一套开放的标准协议,定义了 AI 应用如何与外部工具和数据源进行交互。可以把它理解为"工具调用的 USB-C 接口"——不管是 Java 写的、Python 写的、还是 Go 写的,只要遵循 MCP 协议,任何 AI 应用都能插上用。
1.2、MCP定义
MCP/模型上下文协议(Model Context Protocol)是由 Anthropic 在 2024 年 11 月提出并开源的开放协议,用于统一大语言模型与外部系统的交互方式。
本质上,它解决的是"工具的定义与消费分离"的问题。传统模式是:每个 AI 应用自己定义和实现工具。MCP 模式是:
- 工具的生产者:把能力封装成标准化的 MCP Server,独立部署、按需暴露
- 工具的消费者:任何支持 MCP 协议的 AI 应用(Claude Desktop、VS Code 插件、LangChain4j、LangChain)都能发现并调用这些工具
你可以类比成"微服务"和"SOA服务治理"的关系——把能力变成服务,让多个消费方复用。区别在于 MCP 服务是为 AI 模型特别设计的,它不仅暴露可执行函数,还暴露数据资源和提示词模板,且能被大模型自主发现和决策调用。
1.3、核心架构
MCP 的整体架构是经典的 Client-Server 模式,但扩展成了三层结构,用一个图来理解:
三个角色的具体职责:
- MCP Host:最终面向用户的应用。它不直接处理MCP协议消息,而是通过内置的一个或多个MCP Client与外部Server通信。Host负责管理 Client 的生命周期、权限控制、以及将工具调用结果整合进大模型的上下文,如Claude Desktop、VS Code 插件、自己写好的 Spring Boot 服务
- MCP Client:协议的消费端,每个 Client 与一个 Server 维持一对一的连接。负责协议层面的消息序列化/反序列化、连接保活、请求路由,如 LangChain4j 的 McpClient
- MCP Server:轻量级服务进程,暴露出自己拥有的能力。每个 Server 通常只围绕一个主题设计——比如一个 Server 专门连接 PostgreSQL 暴露数据库查询工具,另一个专门对接文件系统,如 filesystem-server、github-server、自己写的业务 Server
一个Host可以同时连接多个Server,这是MCP强大之处——AI应用可以"即插即用"地接入各种工具和数据源。
官方文档:MCP Architecture
二、MCP核心概念
2.1、三大能力原语
MCP Server 可以暴露三种类型的"能力"(即 Primitives):
| 原语 | 类比笔记(三)中的概念 | 谁触发 | 说明 |
|---|---|---|---|
| Tools | @Tool 标注的方法 |
模型主动 | 可执行的函数。大模型根据用户问题自主决定是否调用、何时调用、传什么参数。这是最核心、最常用的原语 |
| Resources | 类似RAG中的知识库文档 | 模型按需 | 暴露结构化或非结构化数据(如文件内容、数据库记录),模型可以在需要时读取这些数据作为上下文参考。Resources用URI标识,支持多种MIME类型 |
| Prompts | 类似 @SystemMessage 模板 |
用户触发 | 服务器预设的提示词模板。用户可以主动选择使用(比如在Claude Desktop中输入"/summarize"这样的快捷指令),不是由模型自主决策调用的 |
Tools 最常用,它跟 @Tool 一样,是模型自主决策调用的。
Resources 更适合暴露知识库数据,让模型在读到用户问题后,主动要求读取某些数据来增强回答(不是模型自己发一个"我要读文件X"的请求,而是用户问题触发、模型知道该读取什么数据)。
Prompts 则是用户层面的快捷操作,比如用户输入 /translate zh-cn,Client 自动把这条消息替换成一个预设的"请将以下内容翻译成中文"的提示词模板。
需要注意的是:LangChain4j 的 MCP 客户端主要实现的是 Tools 能力,Resources 和 Prompts 支持程度有限(后面"注意事项"章节会提到)。
2.2、传输机制(Transport)
MCP 支持两种底层传输方式,在初始化 Client 或 Server 时指定:
| 传输方式 | 原理 | 连接方式 | 适用场景 |
|---|---|---|---|
| stdio | 标准输入/输出流 | Client 作为子进程启动 Server,通过 stdin/stdout 进行 JSON-RPC 通信 | 本地进程间通信,无需网络配置。适合开发调试,也是Claude Desktop的默认方式 |
| HTTP/SSE | Server-Sent Events,基于 HTTP 长连接 | Client通过 HTTP POST 发送请求,Server通过 SSE(Server-Sent Events)长连接推送响应和通知 | 远程服务通信,跨机器部署。适合生产环境,Server 可独立扩缩容 |
stdio 模式的优点是无需网络、无需端口配置,Client 启动 Server 就像启动一个普通子进程一样。缺点是 Server 的生命周期由 Client 管理,Server 挂了 Client 也得重连,且无法跨机器共享。
HTTP/SSE 模式的优点是服务独立部署、可被多客户端共享、便于监控和扩缩容。缺点是需要处理网络延迟、超时、断线重连等问题。
官方文档:Transport Layer
2.3、通信流程
一次完整的 MCP 工具调用流程是 JSON-RPC 协议驱动的:
和 LangChain4j 中的 @Tool 相比,MCP 多了一层 Client 与 Server 之间的协议交互。整个通信过程可以分为四个阶段:
阶段一:初始化(Initialize)
Client启动后首先发送 initialize 请求,携带自己的协议版本和能力声明。Server 回复自己的协议版本和能力列表:
// Client → Server
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "0.1.0",
"capabilities": {},
"clientInfo": { "name": "my-app", "version": "1.0.0" }
}
}
// Server → Client
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "0.1.0",
"capabilities": { "tools": {} },
"serverInfo": { "name": "filesystem-server", "version": "1.0.0" }
}
}
初始化完成后,Client 发送 initialized 通知(注意:是通知,不是请求,不需要 Server 回复)。
阶段二:能力发现
Client 通过 tools/list 请求获取 Server 暴露的工具列表、资源列表、提示词列表:
// Client → Server
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }
// Server → Client
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "read_file",
"description": "读取指定文件的内容",
"inputSchema": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "文件路径" }
},
"required": ["path"]
}
}
]
}
}
返回的 inputSchema 用的是 JSON Schema 格式,跟笔记(三)中 JsonObjectSchema 定义的结构本质上是同一套东西。
阶段三:工具调用
当大模型决定调用某个工具时,Client 发送 tools/call 请求:
// Client → Server
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": { "path": "/home/user/notes.txt" }
}
}
// Server → Client
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{ "type": "text", "text": "这是文件的内容..." }
]
}
}
阶段四:关闭连接
Client 或 Server 任意一方可以发送 close 通知来终止连接。
整个流程跟 Function Calling 的"模型决定→框架执行→结果返回→模型再推理"是同一个套路,只是执行环节从"本地调用 Java 方法"变成了"通过 JSON-RPC 调用远程服务"。理解了这个对应关系,MCP 就一点都不神秘了。
官方文档:MCP Protocol
三、LangChain4j MCP客户端
官方文档:MCP | LangChain4j
3.1、Maven依赖
LangChain4j 为 MCP 提供的封装层是 langchain4j-mcp 模块,可以处理 JSON-RPC 的序列化、连接管理、Transport切换等底层细节,把 MCP Server 的工具以 ToolSpecification + ToolExecutor 的形式暴露出来,无缝对接 LangChain4j 的函数调用体系。引入这个模块:
<!-- LangChain4j-MCP 核心模块 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-mcp</artifactId>
<!-- 版本由 BOM 管理,推荐使用与 LangChain4j 核心一致的版本 -->
</dependency>
3.2、核心类介绍
LangChain4j MCP 客户端相关的核心类:
| 类名 | 职责 | 官方文档 |
|---|---|---|
| McpClient | MCP客户端顶层接口,定义了连接、断开、获取工具列表、执行工具等核心操作 | McpClient |
| DefaultMcpClient | McpClient的默认实现,通过 Builder 模式构建,底层管理Transport连接 |
DefaultMcpClient |
| McpToolProvider | 实现 ToolProvider 接口,把 MCP 工具桥接到 LangChain4j 的工具调用机制 |
McpToolProvider |
| McpServer | MCP Server构建器,用于把 Java 端的 @Tool 方法暴露为MCP Server(服务端角色) |
McpServer |
| Transport | 传输方式的抽象,有 StdioTransport 和 SseTransport 两种实现 |
Transport |
其中 McpToolProvider 是关键的桥接组件——它把 MCP Server 暴露的工具列表转换成 LangChain4j 的 ToolProvider 接口,大模型就能像调用 @Tool 一样调用 MCP 工具。
3.3、Stdio传输(本地进程)
Stdio 传输适合连接本地运行的 MCP Server,以 Anthropic 官方提供的 filesystem-server 为例:
首先启动 filesystem-server
确保已安装 Node.js,然后:
# 克隆官方 MCP 仓库
git clone https://github.com/modelcontextprotocol/servers.git
cd servers/src/filesystem
# 安装依赖并构建
npm install
npm run build
# 启动 Server(阻塞运行)
npm run start
默认情况下它监听标准输入输出。现在我们用 LangChain4j 连接它。
Java 代码示例
// 创建 stdio 传输方式
Transport stdioTransport = Transport.stdio();
// 构建 MCP Client
McpClient mcpClient = DefaultMcpClient.builder()
.transport(stdioTransport)
.build();
// 连接并初始化(发送 initialize 请求,获取 Server 的 capabilities)
mcpClient.connect();
// 打印该 Server 暴露的工具列表
List<ToolSpecification> tools = mcpClient.listTools();
System.out.println("filesystem-server 暴露了以下工具:");
tools.forEach(tool ->
System.out.println(" - " + tool.name() + ": " + tool.description())
);
// 输出示例:
// filesystem-server 暴露了以下工具:
// - read_file: 指定路径的文件内容读取
// - write_file: 向指定路径写入文件内容
// - list_directory: 列出指定目录下的文件和子目录
// 关闭连接
mcpClient.close();
关键点:
McpClient.connect()会阻塞当前线程(不能在阻塞的 HTTP 请求线程中直接调用),通常需要在独立线程里操作或使用异步 API。
如果希望 Client 自己启动 Server 进程,可以用 Transport.stdio(ProcessBuilder):
Transport stdioTransport = Transport.stdio(new ProcessBuilder("node", "/path/to/filescript"));
McpClient client = DefaultMcpClient.builder()
.transport(stdioTransport)
.build();
client.connect(); // 这会自动启动 filesystem-server 并建立连接
3.4、HTTP/SSE传输(远程服务)
HTTP/SSE 传输适用于生产环境部署。假设已有一个远程 MCP Server 运行在 http://mcp-server.example.com:8080/sse:
// 创建 SSE 传输方式
Transport sseTransport = Transport.sse("http://mcp-server.example.com:8080/sse");
// 构建 MCP Client,设置超时和重试策略
McpClient mcpClient = DefaultMcpClient.builder()
.transport(sseTransport)
.timeout(Duration.ofSeconds(30)) // 单次请求超时
.maxRetries(3) // 重试次数
.build();
// 建立连接
mcpClient.connect();
// 获取工具列表并打印
List<ToolSpecification> remoteTools = mcpClient.listTools();
remoteTools.forEach(tool ->
System.out.println("远程 Server 工具: " + tool.name() + " - " + tool.description())
);
// 演示手动调用一个工具(通常交给大模型自动调用)
ToolExecutionRequest request = ToolExecutionRequest.builder()
.name("get_weather")
.arguments("{\"city\": \"北京\"}")
.build();
String result = mcpClient.executeTool(request);
System.out.println("工具执行结果: " + result);
// 关闭连接
mcpClient.close();
SSE模式下,Client与Server之间通过HTTP POST发送JSON-RPC请求,Server通过 /sse 端点推送响应和通知。两者之间的通信流程完全遵循2.3节中描述的JSON-RPC生命周期。
SSE模式的好处是Server可以独立部署、多Client共享、独立扩缩容,生产环境的首选。额外需要注意的就是连接保活和断线重连——网络不像进程内通信那么可靠,实战中要做好异常处理,后面第八节会细说。
四、MCP与AI Service集成
4.1、McpToolProvider
McpToolProvider 是 LangChain4j 提供的适配器,把 MCP 工具桥接到 ToolProvider 机制:
McpClient mcpClient = DefaultMcpClient.builder()
.transport(Transport.sse("http://localhost:8080/sse"))
.build();
mcpClient.connect();
// 创建 McpToolProvider
ToolProvider mcpToolProvider = new McpToolProvider(mcpClient);
// McpToolProvider 自动实现 ToolProvider 接口:
// - 在每次请求时调用 mcpClient.listTools() 获取最新工具列表
// - 把每个 ToolSpecification 匹配到一个 ToolExecutor
// - 当模型调用工具时,Executor 内部通过 mcpClient.executeTool() 转发请求
// 之后就可以在 AI Service 中使用了
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.toolProvider(mcpToolProvider) // 装载 MCP 工具
.build();
McpToolProvider 会自动维护工具的"及时性"——每次请求时重新获取工具列表,确保如果 Server 在运行时新增了工具,Client 能立即感知到。
4.2、声明式集成(@AiService)
在使用 @AiService 注解时,可以通过 toolProvider Bean 注入 MCP 工具:
先声明 ToolProvider Bean
@Configuration
public class McpToolProviderConfig {
@Bean
public Transport mcpTransport() {
// 连接远程 MCP Server(如 filesystem-server 部署在 8080 端口)
return Transport.sse("http://localhost:8080/sse");
}
@Bean
public McpClient mcpClient(Transport transport) {
McpClient client = DefaultMcpClient.builder()
.transport(transport)
.timeout(Duration.ofSeconds(30))
.maxRetries(3)
.build();
client.connect(); // 在 Bean 初始化时建立连接
return client;
}
@Bean
@ConditionalOnBean(McpClient.class)
public ToolProvider mcpToolProvider(McpClient mcpClient) {
return new McpToolProvider(mcpClient);
}
}
然后在 AI Service 中注入
@AiService(wiringMode = EXPLICIT,
chatModel = "qwenChatModel",
toolProvider = "mcpToolProvider") // 注入 MCP 工具
public interface FileAssistant {
@SystemMessage("你是一个文件操作助手")
String chat(@MemoryId String memoryId, String userMessage);
}
当用户输入"帮我读取 /home/user/note.txt 的内容"时,大模型会发现 read_file 这个 MCP 工具可用,自主发起调用,McpToolProvider 会把请求转发给远程 filesystem-server 执行,最终把文件内容取回来。
完整的 Controller 调用示例
@RestController
public class ChatController {
@Autowired
private FileAssistant fileAssistant;
@GetMapping("/chat")
public String chat(@RequestParam("userId") String userId,
@RequestParam("message") String message) {
return fileAssistant.chat(userId, message);
}
}
4.3、编程式集成(AiServices.builder)
如果不想依赖 Spring 的依赖注入机制,也可以用编程式建造者模式:
// 创建 MCP Client
McpClient mcpClient = DefaultMcpClient.builder()
.transport(Transport.sse("http://localhost:8080/sse"))
.build();
mcpClient.connect();
// 创建 ToolProvider
ToolProvider mcpToolProvider = new McpToolProvider(mcpClient);
// 构建 AI Service
Assistant assistant = AiServices.builder(Assistant.class)
.chatModel(qwenChatModel)
.toolProvider(mcpToolProvider)
.build();
// 对话
String answer = assistant.chat("帮我读取 /home/user/note.txt");
System.out.println(answer);
// 记得在应用退出时关闭 MCP Client
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
mcpClient.close();
} catch (Exception e) {
// ignore
}
}));
注意:编程式集成时,
McpClient的生命周期需要你自己管理(调用close()以释放资源、关闭连接)。声明式集成中 Spring 会在容器销毁时自动调用 Bean 的销毁方法(如果你的 Bean 实现了DisposableBean或使用了@PreDestroy)。
五、MCP Server
官方文档:MCP Servers
5.1、官方MCP Server
Anthropic 提供了一批参考 MCP Server,覆盖常见场景:
| Server | 功能 | 适用场景 |
|---|---|---|
| filesystem-server | 文件系统操作(读、写、遍历目录) | 本地文件处理、配置文件读取 |
| github-server | 通过 GitHub API 操作仓库(issues、PRs、代码) | 代码审查、自动化 PR 管理 |
| sqlite-server | SQLite 数据库操作 | 本地轻量级数据查询 |
| postgres-server | PostgreSQL 数据库操作 | 企业级数据库集成 |
| slack-server | Slack API 集成 | 聊天机器人自动化 |
| brave-search-server, google-maps-server | 外部 API 集成 | 搜索、地图、地理信息 |
这些 Server 都已用 Python SDK 或 TypeScript SDK 实现过,你可以直接拿来用,也能基于它们修改。
使用 filesystem-server 示例
官方仓库:modelcontextprotocol/servers
# 克隆并构建
git clone https://github.com/modelcontextprotocol/servers.git
cd servers/src/filesystem
npm install
npm run build
# 指定允许访问的根目录并启动(安全考虑,不要开放整个文件系统)
ALLOWED_DIRECTORIES=/home/user/docs,/home/user/data npm run start
启动后,用 LangChain4j 连接它,就能让大模型只读指定路径下的文件,不会越权访问系统其他位置。
5.2、自定义MCP Server
除了使用官方 Server,你还可以用 LangChain4j 把现有的 Java 工具类暴露为 MCP Server。好消息是——如果你的工具已经用 @Tool 注解写好了,代码基本不用改:
// 已有的工具类,不需要任何修改
@Component
public class WeatherTools {
@Tool(name = "weather_query", value = "查询指定城市的实时天气信息,参数city为城市名称")
public String queryWeather(@P("城市名称") String city) {
// 实际项目中这里调用天气API
return city + "的天气:晴,温度25°C,湿度60%";
}
@Tool(name = "weather_forecast", value = "查询指定城市未来3天的天气预报")
public String forecast(@P("城市名称") String city) {
return city + "未来三天:周一晴、周二多云、周三小雨";
}
}
然后用 McpServer 把这个工具类包装并启动:
public class WeatherMcpServerApplication {
public static void main(String[] args) {
WeatherTools weatherTools = new WeatherTools();
// 构建MCP Server,注册工具
McpServer server = McpServer.builder()
.name("weather-server") // Server标识名
.version("1.0.0") // 版本号
.tools(weatherTools) // 注册包含@Tool方法的对象,可以传多个
.build();
// 以stdio模式启动——Server阻塞等待Client连接
server.start(McpTransport.stdio());
}
}
把这个程序打成JAR包,就能被任何MCP Client以stdio模式加载了。比如 Claude Desktop 配置:
{
"mcpServers": {
"weather": {
"command": "java",
"args": ["-jar", "/path/to/weather-mcp-server.jar"]
}
}
}
如果需要在网络上暴露,切换为SSE模式:
server.start(McpTransport.sse(8080)); // 监听8080端口,提供SSE端点
这样一个用 Java 写的工具类就成了标准的 MCP Server——任何语言的 MCP Client 都能发现并调用它。这种"写一次、到处用"的体验,说实话比之前 @Tool 单打独斗的模式舒服太多了。
LangChain4j MCP Server文档:MCP Server | LangChain4j
六、MCP与Function Calling对比
从 Function Calling 到 MCP,其实是一个从"工具内嵌"到"工具服务化"的演进。总结一下两者的差异:
| 维度 | @Tool / ToolProvider(笔记三) |
MCP(笔记五) |
|---|---|---|
| 工具位置 | 在应用进程内,直接调用 Java 方法 | 独立服务进程,通过网络或 stdio 通信 |
| 耦合度 | 与应用强耦合(同一 JVM) | 与应用解耦(独立部署) |
| 跨语言复用 | 仅限 Java | 任何语言(Python、 TypeScript、Go) |
| 调试复杂度 | 低,IDE 中直接打断点 | 中,跨进程调试需要额外工具(日志、远程断点) |
| 性能开销 | 无网络开销,纯本地调用 | 有传输开销(stdio 可忽略,SSE 有 HTTP 延迟) |
| 发现机制 | 编译期/启动时静态注册 | 运行时动态发现(每次请求可获取最新工具列表) |
| 生态共享 | 不同框架各玩一套 | 统一协议,跨框架共享工具 |
| 部署运维 | 随应用一起宕、一起起 | 可独立扩缩容、独立监控 |
| 适用场景 | 单体应用、快速开发、对性能敏感 | 微服务架构、跨项目共享、构建工具生态 |
实际选型建议:两者不是非此即彼的关系。内部工具(比如订单查询、用户信息获取)用
@Tool更直接;共享工具或多平台工具(如天气查询、股票行情、数据库访问)用 MCP 更合适。你可以在一个项目里同时用@Tool和 MCP,框架会自动合并两者的工具列表。
七、注意事项
MCP 虽然强大,但在 LangChain4j 的当前版本(1.14.0-beta24)中还有一些限制和需要注意的地方:
Resources 和 Prompts 支持有限
LangChain4j 的 MCP 客户端目前主要实现的是 Tools 能力。Resources(资源读取)和 Prompts(提示词模板)的集成仍在开发中或支持不完整。这意味着:
- 你可以从 MCP Server 获取
resources/list和prompts/list的列表 - 但把 Resources 和 Prompts 自动注入到 AI Service 的机制可能还不完善
- 如果你需要在 LangChain4j 中使用 Resources/Prompts,可能需要手动处理(自己调用
mcpClient.readResource()或mcpClient.getPrompt(),然后手动组装到Prompt里)
官方 GitHub Issue: Support for Resources and Prompts
建议关注官方文档的更新,这两大原语的集成应该在后续版本逐步完善。
网络与进程管理
- stdio 模式:MCP Server 进程的生命周期由 Client 管理,Server 挂了或手动 killed 了,Client 会失去连接。可考虑用进程管理器(systemd、supervisord)来确保 Server 自动重启
- HTTP/SSE 模式:SSE 连接基于 HTTP 长连接,中间网络抖动或 Server 重启可能导致连接断开。建议:
- 设置合理的
timeout(单次请求超时)和maxRetries(重试次数) - 在调用层做兜底,比如工具超时时返回友好提示"服务暂时不可用,请稍后再试"
- 使用长连接保活(HTTP Keep-Alive),减少频繁重连开销
- 设置合理的
与流式输出的兼容性
当同时启用 Streaming 和 MCP 工具调用时,流式输出会被暂时阻塞——直到工具调用完成后、大模型生成最终答案时,才开始流式吐出文本。这是当前 LangChain4j 的设计限制,因为工具调用需要等待 Server 返回结果,无法提前开始流式生成。
如果你对"流式+工具"的体验要求很高(比如边调用工具边显示"正在调用工具..."这类提示),可能需要手动做 UI 层的提示优化,而不是依赖框架内置能力。
工具列表变化时的行为
McpToolProvider 会在每次请求时重新获取工具列表,这意味着:
- 如果 MCP Server 在运行时新增/修改/删除了工具,Client 能立即感知到
- 但这也带来了一些性能开销——每次请求都要发一个 JSON-RPC
tools/list请求 - 对比
@Tool的静态工具列表,MCP 工具是动态的,适合工具经常变化的场景(比如插件系统)
如果你的工具列表非常稳定且繁重,可以考虑缓存工具列表到内存,用一个定时任务定期刷新。
版本兼容性
MCP 协议本身在快速演进,LangChain4j 的 MCP 客户端实现也可能有 Breaking Change。使用时注意:
- 锁定 LangChain4j 版本,频繁升级可能导致 API 变动
- 关注 LangChain4j 的 Changelog 和 GitHub Release Notes
- 如果遇到
DefaultMcpClient.builder()的参数在升级后不可用了,以最新官方文档为准
安全性考虑
- Allowed Directories:连接 filesystem-server 时务必限定允许访问的目录,不要开放整个根路径(如
ALLOWED_DIRECTORIES=/tmp/docs而不是/) - 参数校验:自定义 Server 时对输入参数做校验,避免 SQL 注入、路径遍历等安全问题
- 鉴权与限流:生产环境的 MCP Server 应考虑接入鉴权机制(API Key、OAuth),避免被滥用;对高频调用做限流
参考资料:
官方英文文档:Get Started | LangChain4j,API 文档:Overview (LangChain4j)
非官方中文文档:快速开始 | LangChain4j 中文文档
评论 (0)