模型上下文协议 (MCP)
LangChain4j 支持 Model Context Protocol (MCP),用于与兼容 MCP 的服务器通信,这些服务器可以提供并执行工具。
协议的一般信息可以在 MCP 官方网站 上找到。
该协议规定了两种传输方式,LangChain4j 都支持:
- 可流式 HTTP:
客户端发送 HTTP 请求,服务器可以返回常规响应,或者在需要持续发送多个响应时打开 SSE 流。 - stdio:
客户端可以作为本地子进程运行 MCP 服务器,并通过标准输入/输出与其通信。
LangChain4j 还支持 旧版的 HTTP/SSE 传输,
但该方式已被弃用,将在未来移除。
要让你的聊天模型或 AI 服务运行 MCP 服务器提供的工具,需要创建一个 MCP 工具提供者实例。
创建 MCP 工具提供者
MCP 传输 (Transport)
首先,需要一个 MCP Transport 实例。
使用 stdio —— 示例展示如何通过 NPM 包以子进程方式启动服务器:
McpTransport transport = new StdioMcpTransport.Builder()
.command(List.of("/usr/bin/npm", "exec", "@modelcontextprotocol/server-everything@0.6.2"))
.logEvents(true) // 是否在日志中打印流量
.build();
使用可流式 HTTP 传输 —— 需要提供服务器的 POST
端点 URL:
McpTransport transport = new StreamableHttpMcpTransport.Builder()
.url("http://localhost:3001/mcp")
.logRequests(true) // 打印请求
.logResponses(true) // 打印响应
.build();
注意:
Streamable HTTP 传输 目前不会创建全局 SSE 流(参见 规范)。
这意味着,依赖 服务器主动发起请求/通知 的功能可能无法工作。
如果服务器将请求和通知附带在客户端发起操作时的 SSE 流中,那么这些功能依然能正常运行。
使用旧版 HTTP 传输 —— 有两个 URL:
一个用于启动 SSE 通道,一个用于 POST
提交命令。后者由服务器动态提供,前者需要手动指定:
McpTransport transport = new HttpMcpTransport.Builder()
.sseUrl("http://localhost:3001/sse")
.logRequests(true)
.logResponses(true)
.build();
MCP 客户端
从 transport 创建 MCP 客户端:
McpClient mcpClient = new DefaultMcpClient.Builder()
.key("MyMCPClient")
.transport(transport)
.build();
key
是可选的,但推荐设置,尤其当存在多个 MCP 客户端时,用于区分。
MCP 工具提供者
从客户端创建 MCP 工具提供者:
McpToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(mcpClient)
.build();
一个 MCP 工具提供者可以同时使用多个客户端。
在这种情况下,可以指定容错策略:
.builder().failIfOneServerFails(boolean)
- 默认为
false
→ 忽略某个服务器的错误,继续使用其他服务器。 - 设置为
true
→ 任意服务器出错都会导致抛出异常。
此外,MCP 服务器可能提供大量工具,而某个 AI 服务只需要少数几个。
可以使用工具过滤机制来避免调用不需要的工具,并减少幻觉风险:
McpToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(mcpClient)
.filterToolNames("get_issue", "get_issue_comments", "list_issues")
.build();
这样,AI 服务只能使用这 3 个工具(读取 issue 和评论),而不能创建新的 issue。
更通用的方式是通过 BiPredicate<McpClient, ToolSpecification>
过滤工具。例如,两个 MCP 客户端都提供名为 echoInteger
的工具,可以指定只保留 numeric-mcp
的版本:
McpToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(mcpClient1, mcpClient2)
.filter((mcpClient, tool) ->
!tool.name().startsWith("echoInteger") ||
mcpClient.key().equals("numeric-mcp"))
.build();
注意:在同一个 builder 上多次调用
filter
会形成逻辑 AND 关系。
此外,可以在运行时动态添加/移除客户端和过滤器。
将工具提供者绑定到 AI 服务:
Bot bot = AiServices.builder(Bot.class)
.chatModel(model)
.toolProvider(toolProvider)
.build();
或者直接提供工具映射:
Map<ToolSpecification, ToolExecutor> tools = mcpClient.listTools().stream().collect(Collectors.toMap(
tool -> tool,
tool -> new McpToolExecutor(mcpClient)
));
绑定到 AI 服务:
Bot bot = AiServices.builder(Bot.class)
.chatModel(model)
.tools(tools)
.build();
更多关于工具支持的信息见 教程。
日志
MCP 协议还定义了服务器向客户端发送日志消息的方式。
默认情况下,客户端会将这些日志转换为 SLF4J 日志。
如果需要自定义,可以实现 dev.langchain4j.mcp.client.logging.McpLogMessageHandler
,并在构建 MCP 客户端时传入:
McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.logMessageHandler(new MyLogMessageHandler())
.build();
资源 (Resources)
资源的使用有两种方式:
- 编程式访问:直接调用 MCP 客户端方法。
- 自动暴露为工具:通过合成工具,让 LLM 自主调用。
编程式访问
- 列出资源:
client.listResources()
- 列出资源模板:
client.listResourceTemplates()
返回 McpResource
或 McpResourceTemplate
对象,包含 URI 等元数据。
- 读取资源内容:
client.readResource(uri)
返回McpReadResourceResult
,其中包含McpResourceContents
列表。
每个内容可能是二进制 (McpBlobResourceContents
) 或文本 (McpTextResourceContents
)。
自动暴露为合成工具
如果在构建 McpToolProvider
时设置了 McpResourcesAsToolsPresenter
,工具提供者会自动增加两个合成工具:
list_resources
:列出所有资源get_resource
:读取指定资源内容(需要 MCP 服务器名和 URI)
LangChain4j 提供默认实现 DefaultMcpResourcesAsToolsPresenter
。
示例输出:
[ {
"mcpServer" : "alice",
"uri" : "file:///info",
"uriTemplate" : null,
"name" : "basicInfo",
"description" : "Basic information about Alice",
"mimeType" : "text/plain"
}, {
"mcpServer" : "bob",
"uri" : "file:///info",
"uriTemplate" : null,
"name" : "basicInfo",
"description" : "Basic information about Bob",
"mimeType" : "text/plain"
} ]
每个资源由 (mcpServer, uri)
唯一标识。
合成工具的默认描述通常足够,但可以通过 DefaultMcpResourcesAsToolsPresenter.Builder
自定义。
提示词 (Prompts)
- 获取服务器提示词列表:
client.listPrompts()
→ 返回List<McpPrompt>
- 渲染提示词内容:
client.getPrompt(name, arguments)
一个提示词可能包含多个消息 → McpPromptMessage
支持内容类型:McpTextContent
, McpImageContent
, McpEmbeddedResource
。
可以用 McpPromptMessage.toChatMessage()
转换为通用 ChatMessage
,但有一些限制:
- 如果角色是
assistant
且内容不是文本 → 抛出异常 - 含二进制内容 → 无法转换
通过 Docker 使用 GitHub MCP 服务器
示例:使用 MCP 桥接 AI 模型与 GitHub 工具,获取并总结仓库的最新提交。
GitHub MCP 服务器已有现成实现:GitHub MCP server。
源代码在 MCP GitHub 仓库。
打包和运行 GitHub MCP 服务器 (Docker)
获取源码:
docker build -t mcp/github -f src/github/Dockerfile .
构建完成后,本地镜像为 mcp/github
:
docker image ls
REPOSITORY TAG IMAGE ID SIZE
mcp/github latest b141704170b1 173MB
开发工具提供者
创建 McpGithubToolsExample
类,功能包括:
- 启动 GitHub MCP 服务器(Docker 容器)
- 建立 stdio 传输连接
- 使用 LLM 总结 LangChain4j 仓库最近 3 次提交
注意:代码中通过环境变量
GITHUB_PERSONAL_ACCESS_TOKEN
传入 GitHub Token。
但对于公共仓库的一些操作,该 Token 不是必须的。
实现:
public static void main(String[] args) throws Exception {
ChatModel model = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.logRequests(true)
.logResponses(true)
.build();
McpTransport transport = new StdioMcpTransport.Builder()
.command(List.of("/usr/local/bin/docker", "run", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "-i", "mcp/github"))
.logEvents(true)
.build();
McpClient mcpClient = new DefaultMcpClient.Builder()
.transport(transport)
.build();
ToolProvider toolProvider = McpToolProvider.builder()
.mcpClients(List.of(mcpClient))
.build();
Bot bot = AiServices.builder(Bot.class)
.chatModel(model)
.toolProvider(toolProvider)
.build();
try {
String response = bot.chat("Summarize the last 3 commits of the LangChain4j GitHub repository");
System.out.println("RESPONSE: " + response);
} finally {
mcpClient.close();
}
}
注意:示例使用 Docker(路径
/usr/local/bin/docker
),不同操作系统需调整路径。
如需使用 Podman,可相应替换命令。
执行代码
运行前需确保:
- Docker 已启动
- 设置了环境变量
OPENAI_API_KEY
运行后,输出类似:
Here are the summaries of the last three commits in the LangChain4j GitHub repository:
1. Commit 36951f9 (2025-02-05) by Dmytro Liubarskyi
- Updated to `upload-pages-artifact@v3`
2. Commit 6fcd19f (2025-02-05) by Dmytro Liubarskyi
- Updated to `checkout@v4`, `deploy-pages@v4`, `upload-pages-artifact@v4`
3. Commit 2e74049 (2025-02-05) by Dmytro Liubarskyi
- Updated to `setup-node@v4`, `configure-pages@v4`
全部由同一作者在同一天提交,主要更新 GitHub Actions 版本。
不使用 AI 服务直接调用 MCP
除了高层 API,也可以通过底层 API 手动调用 MCP。
示例:列出工具并执行 ChatRequest:
List<ToolSpecification> toolSpecifications = mcpClient.listTools();
ChatRequest chatRequest = ChatRequest.builder()
.messages(UserMessage.from("What will the weather be like in London tomorrow?"))
.toolSpecifications(toolSpecifications)
.build();
ChatResponse response = chatModel.chat(chatRequest);
AiMessage aiMessage = response.aiMessage();
if(aiMessage.hasToolExecutionRequests()) {
for (ToolExecutionRequest req : aiMessage.toolExecutionRequests()) {
String resultString = mcpClient.executeTool(req);
ToolExecutionResultMessage resultMessage = ToolExecutionResultMessage.from(req.id(), req.name(), resultString);
}
}
直接执行某个工具:
ToolExecutionRequest request = ToolExecutionRequest.builder()
.name("tool1")
.arguments("{\"a\": \"b\"}")
.build();
String toolResult = mcpClient.executeTool(request);
工具缓存说明
DefaultMcpClient
内部维护工具缓存。
默认情况下,工具列表在首次获取后不会再次请求,除非服务器发通知更新。
可以手动清除缓存:
mcpClient.evictToolListCache();
也可以在构建时禁用缓存:
McpClient mcpClient = new DefaultMcpClient.Builder()
.key("MyMCPClient")
.transport(transport)
.cacheToolList(false)
.build();