Skip to content
LangChain4j 文档版本 1.22.0 源快照 2b8d48cd 更新于 2026-10-14

Model Context Protocol (MCP)

NOTE

MCP 客户端可以通过 executeToolAsync(...) 在不阻塞线程的情况下执行工具,供异步与响应式 AI 服务模式使用。参见非阻塞与响应式。

LangChain4j 支持 Model Context Protocol(MCP),以与能提供并执行工具的、符合 MCP 的服务器通信。关于该协议的总体信息可在 MCP 网站 找到。

NOTE

想用一个 Java 构建 MCP 服务器?参见构建一个 Java MCP 服务器。

该协议规定了两种传输类型,二者都受支持:

  • Streamable HTTP:客户端发送 HTTP 请求,服务器要么以一个常规响应回复,要么在需要随时间发送多个响应时打开一个 SSE 流。
  • stdio:客户端可以把一个 MCP 服务器作为本地子进程运行,并通过标准输入/输出直接与它通信。

在规范之上,LangChain4j 还实现了 WebSocket 传输。这种传输并非标准化,且目前客户端一侧的实现方式与 Quarkus MCP Server 扩展 所实现的 WebSocket 传输兼容。对于用其他框架构建的、暴露 WebSocket 的 MCP 服务器,兼容性无法保证。

此外,LangChain4J 支持一种 Docker stdio 传输,它能使用以容器镜像分发的 stdio MCP 服务器。

要让你的聊天模型或 AI 服务运行由某个 MCP 服务器提供的工具,你需要创建一个 MCP 工具提供者的实例。

创建一个 MCP 工具提供者

MCP 传输

首先,你需要一个 MCP 传输的实例。

对于 stdio —— 这个示例展示了如何把一个 NPM 包作为子进程启动一个服务器:

java
McpTransport transport = StdioMcpTransport.builder()
    .command(List.of("/usr/bin/npm", "exec", "@modelcontextprotocol/server-everything@0.6.2"))
    .logEvents(true) // only if you want to see the traffic in the log
    .build();

若服务器需要在某个特定的项目目录中运行,请在 stdio 传输 builder 上配置 .workingDirectory(Path.of("/path/to/project"))。该目录在启动与重启时都作用于服务器子进程。省略时,子进程会继承当前进程的工作目录。

对于 Streamable HTTP 传输,你需要提供服务器 POST 端点的 URL:

java
McpTransport transport = StreamableHttpMcpTransport.builder()
        .url("http://localhost:3001/mcp")
        .logRequests(true) // if you want to see the traffic in the log
        .logResponses(true)
        .build();

注:(仅旧协议)Streamable HTTP 传输可以选择性地打开一条辅助的 基于 GET 的 SSE 流,以接收服务器主动发起的通知与请求。用 builder 上的 .subsidiaryChannel(true) 启用它。在现代化协议(2026-07-28)下,通知改经 subscriptions/listen 接收。它默认禁用。若服务器不支持它,传输会记录一条警告并在没有它的情况下继续。若流在建立之后断开,传输会自动重连(遵循服务器的 retry 值,默认为 5 秒)。

对于 WebSocket 传输:

java
McpTransport transport = WebSocketMcpTransport.builder()
        .url("ws://localhost:3001/mcp/ws")
        .logResponses(true)
        .logRequests(true)
        .build();

对于 Docker stdio 传输,你首先需要在 pom.xml 中添加一个模块:

xml
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-mcp-docker</artifactId>
</dependency>

然后你需要创建一个 Docker 传输:

java
McpTransport transport = DockerMcpTransport.builder()
    .image("mcp/time")
    .dockerHost("unix:///var/run/docker.sock")
    .logEvents(true) // if you want to see the traffic in the log
    .build();

MCP 客户端

要从传输创建一个 MCP 客户端:

java
McpClient mcpClient = DefaultMcpClient.builder()
    .key("MyMCPClient")
    .transport(transport)
    .build();

请注意,客户端 key 是可选的,但建议设置它,尤其当有多个 MCP 客户端、且需要在它们之间区分时,它是必需的。

协议版本

MCP 客户端同时支持旧协议(2025-11-25)与现代的无状态协议(2026-07-28)。默认情况下,客户端会自动检测服务器的协议版本,当服务器支持时优先选择 2026-07-28:

java
// Auto-detect (default behavior)
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .build();

// Force modern protocol
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .protocolVersion("2026-07-28")
    .build();

// Force legacy protocol
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .protocolVersion("2025-11-25")
    .build();

当客户端启动时,检测会多花费一次往返:它发送一个 server/discover 请求,若应答是错误、或未在 protocolDetectionTimeout 内到达,就把服务器当作旧协议服务器。该超时默认为 initializationTimeout(30 秒),因为作为一个子进程启动的服务器在能回答任何东西之前需要时间启动,而过早放弃会让一个现代服务器看起来像旧服务器。由沉默而非错误导致的回退会被记录为警告,因为那正是服务器可能被误判的情形。

java
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .protocolDetectionTimeout(Duration.ofSeconds(5)) // servers you know answer quickly
    .build();

显式设置 protocolVersion 会完全跳过检测。当你已经知道服务器讲哪个协议版本时,这值得一做;若服务器对收到一个它不认识的方法反应不良,这也是出路:一些较旧的 MCP 服务器实现在收到未知请求时会终止,而不是以一个错误应答。

MCP 工具提供者

最后,从客户端创建一个 MCP 工具提供者:

java
McpToolProvider toolProvider = McpToolProvider.builder()
    .mcpClients(mcpClient)
    .build();

请注意,一个 MCP 工具提供者可以同时使用多个客户端。若你利用这一点,你还可以指定工具提供者在从某个特定服务器获取工具失败时的行为——这通过 builder.failIfOneServerFails(boolean) 方法完成。默认为 false,意味着工具提供者会忽略来自某个服务器的错误并继续处理其他服务器。若你把它设为 true,任何服务器的失败都会导致工具提供者抛出异常。

此外,MCP 服务器常常提供数十个工具,而某个 AI 服务可能只需其中几个,既能防止使用不想要的工具,也能减少产生幻觉的可能。McpToolProvider 允许你按名称过滤这些工具,如下:

java
McpToolProvider toolProvider = McpToolProvider.builder()
    .mcpClients(mcpClient)
    .filterToolNames("get_issue", "get_issue_comments", "list_issues")
    .build();

这样,用该 ToolProvider 配置的 AI 服务就只能使用所提及的那 3 个工具,允许它读取已有的 issue,却阻止它创建新的。更一般地,ToolProvider 允许你通过一个 BiPredicate<McpClient, ToolSpecification> 过滤工具。当多个 MCP 客户端暴露同名因而相互冲突的工具时,这也很有用。例如,下面的 ToolProvider 从两个 MCP 客户端获取工具,但由于它们都有一个名为 echoInteger 的工具,它只取 key 为 numeric-mcp 的那个 MCP 客户端的工具:

java
McpToolProvider toolProvider = McpToolProvider.builder()
    .mcpClients(mcpClient1, mcpClient2)
    .filter((mcpClient, tool) ->
            !tool.name().startsWith("echoInteger") || 
            mcpClient.key().equals("numeric-mcp"))
    .build();

请注意,在同一个 McpToolProvider builder 上多次调用 filter 方法,会产生所有这些过滤条件的合取(AND)。

为了让应用能在运行时连接或断开 MCP 服务器,也可以向一个已有的 McpToolProvider 实例动态地添加与移除客户端和过滤器。

要把一个工具提供者绑定到某个 AI 服务,只需使用 AI 服务 builder 的 toolProvider 方法:

java
Bot bot = AiServices.builder(Bot.class)
    .chatModel(model)
    .toolProvider(toolProvider)
    .build();

或者,你可以用一个 Map<ToolSpecification, ToolExecutor> 提供工具。

java
Map<ToolSpecification, ToolExecutor> tools = mcpClient.listTools().stream().collect(Collectors.toMap(
        tool -> tool, 
        tool -> new McpToolExecutor(mcpClient)
));

要把工具绑定到某个 AI 服务,只需使用 AI 服务 builder 的 tools 方法:

java
Bot bot = AiServices.builder(Bot.class)
    .chatModel(model)
    .tools(tools)
    .build();

关于 LangChain4j 中工具支持的更多信息可在这里找到。

MCP 工具名映射

如果你使用多个 MCP 服务器,而它们暴露了名字冲突的工具(或你只是想调整一个选得不合适的名字),应用一个工具名映射函数可能会有用。这可以在创建 McpToolProvider 时通过指定一个 BiFunction<McpClient, ToolSpecification, String> 来完成。

例如:

java
McpToolProvider toolProvider = McpToolProvider.builder()
        .mcpClients(mcpClient1, mcpClient2)
        .toolNameMapper((client, toolSpec) -> {
            // Prefix all tool names with the name of the MCP client and an underscore
            return client.key() + "_" + toolSpec.name();
        })
        .build();

此后,工具提供者返回的 ToolSpecification 对象会含有被映射的(逻辑)名称,而生成的 ToolExecutor 对象则被固定为在调用工具时把原始(物理)名称传给服务器。

MCP 工具规范映射

与上文 MCP 工具映射类似,也可以映射整个 ToolSpecification:

java
McpToolProvider toolProvider = McpToolProvider.builder()
        .mcpClients(mcpClient)
        .toolSpecificationMapper((client, toolSpec) -> {
            // Prefix all tool names with "myprefix_" and convert the description to uppercase
            return toolSpec.toBuilder()
                .name("myprefix_" + toolSpec.name())
                .description(toolSpec.description().toUpperCase())
                .build();
        })
        .build();

MCP 工具元数据

MCP 协议允许服务器以注解的形式,或以工具定义中的 _meta 字段,为每个工具提供额外的元数据。LangChain4j 通过存储在 ToolSpecification.metadata() 方法内的 map 暴露所有这些元数据。注解会用可在 dev.langchain4j.mcp.client.McpToolMetadataKeys 类中作为常量找到的键存入该 map。_meta 字段的内容用其原始键存储,JSON 值被序列化为嵌套 map。

直接存在于 MCP 工具定义中的 title 字段,会以 McpToolMetadataKeys.TITLE 键暴露在元数据 map 中,以区别于从注解中取回的 title——后者以 McpToolMetadataKeys.ANNOTATION_TITLE 键暴露。

若工具带有图标,它们会以 McpToolMetadataKeys.ICONS 键暴露在元数据 map 中。

MCP 工具结果元数据

除实际结果外,MCP 服务器还能把一个 _meta 对象附加到一次工具调用的响应上。这对你的应用需要、但 LLM 不需要的数据很有用,例如工具所创建记录的标识符,或你的 UI 应当渲染的一个组件。

_meta 对象的条目经由 ToolExecutionResult.attributes() 用其原始键暴露,JSON 值被转换为嵌套 map。它们从不被发送给 LLM。被 MCP 规范保留的键(例如 io.modelcontextprotocol/serverInfo)会被跳过,因为它们描述的是协议交互而非工具结果。

当直接在客户端上调用一个工具时,属性始终可用:

java
ToolExecutionResult result = mcpClient.executeTool(toolExecutionRequest);
Object widget = result.attributes().get("example.org/widget");

当工具由某个 AI 服务调用时,属性默认会被丢弃,因为它们的大小与内容受 MCP 服务器控制。要保留它们,请在工具提供者上启用 returnToolResultAttributes:

java
McpToolProvider toolProvider = McpToolProvider.builder()
    .mcpClients(mcpClient)
    .returnToolResultAttributes(true)
    .build();

随后可以从结果的各工具执行中读取它们:

java
interface Assistant {

    Result<String> chat(String userMessage);
}

Result<String> result = assistant.chat("What is the weather in Munich?");
for (ToolExecution toolExecution : result.toolExecutions()) {
    Object widget = toolExecution.attributes().get("example.org/widget");
}

这些属性也会被传播进 ToolExecutionResultMessage,因此它们与该消息一起被存储在聊天记忆中。当聊天记忆被持久化时请牢记这一点,更多细节见工具结果属性。

若工具返回一个应用级错误(工具结果中的 "isError": true),调用会以一个 ToolExecutionException 的子类 McpApplicationErrorException 结束,而失败响应的 _meta 不可用。它仍会被投递给 McpClientListener.afterExecuteTool(),后者会接收完整的原始响应。

应用级错误是 MCP 服务器告知模型某工具未成功的方式,因此它的文本是为 LLM 而写的。所以 McpApplicationErrorException 实现了 ToolErrorVisibleToLlm:对一个尊重它的工具执行错误处理器(例如 ToolExecutionErrorHandler.failInvocationUnlessVisibleToLlm()),服务器的文本会被发送给 LLM,而它能对其作出反应。用该处理器,一个协议错误(意味着调用本身出了问题)会让 AI 服务调用失败,一个不带任何文本的应用级错误(一个普通的 ToolExecutionException)也是如此。参见错误处理。

作为 HTTP 头携带的工具参数

在经 Streamable HTTP 的 2026-07-28 协议上,服务器可以请求把某些选定的工具参数除请求体外还作为 HTTP 头发送,从而代理与网关能在不解析调用的情况下路由或授权它。服务器会在工具的输入 schema 中用 x-mcp-header 标记这样一个参数:

json
{
  "name": "query_database",
  "inputSchema": {
    "type": "object",
    "properties": {
      "tenant": { "type": "string", "x-mcp-header": "X-Tenant-Id" },
      "sql": { "type": "string" }
    }
  }
}

当调用该工具时,客户端会把 tenant 的值既放进请求体、也放进一个 Mcp-Param-X-Tenant-Id HTTP 头。这会自动发生;无需配置。不适合放进头的值(例如含非 ASCII 字符的)会按协议要求进行 Base64 编码。

只有 string、integer 与 boolean 参数可以这样标记,头名必须是有效的 HTTP token,且同一个头名不得被声明两次。一个其定义违反这些规则的工具会被从 listTools() 中排除并记录一条警告,因为客户端无法判断这样的调用本应如何抵达服务器。

这些头名是从工具定义中读取的,因此客户端在能发送它们之前需要先知道工具列表。若你在一个从未列出过工具的客户端上调用 executeTool(),头会被省略并记录一条警告;请先调用 listTools()。工具提供者会自行这么做。

提供 _meta 字段

MCP 协议允许客户端把一个 _meta 对象附加到发送给服务器的每个请求与通知的 params 上。这可用来传递 OpenTelemetry 追踪上下文、自定义应用元数据,或任何其他服务器可能需要的带外信息。

要提供 _meta 字段,请在客户端 builder 上注册一个 McpMetaSupplier。该供应商会在每个请求或通知之前被调用,所返回的 map 会被放入 params._meta。与 HTTP 头不同,这适用于所有传输(stdio、HTTP、WebSocket)。

java
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .metaSupplier(context -> Map.of(
        "traceparent", "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01",
        "custom-key", "custom-value"))
    .build();

该供应商会接收一个(可空的)McpCallContext,它含有正在发送的消息,以及(如适用)触发它的那次 AI 服务调用的 InvocationContext。这让供应商能根据正在执行的操作来变化元数据。

日志

MCP 协议还定义了服务器向客户端发送日志消息的方式。默认情况下,客户端的行为是转换这些日志消息并用 SLF4J logger 记录它们。若你想改变这一行为,有一个名为 dev.langchain4j.mcp.client.logging.McpLogMessageHandler 的接口,作为所收到日志消息的回调。若你创建自己的 McpLogMessageHandler 实现,请把它传给 MCP 客户端 builder:

java
McpClient mcpClient = new DefaultMcpClient.Builder()
    .transport(transport)
    .logHandler(new MyLogMessageHandler())
    .build();

MCP 监听器

MCP 客户端支持监听器,可监听在客户端生命周期中发生的事件。dev.langchain4j.mcp.client.McpClientListener 接口作为监听器实现的基础。可以在单个客户端上注册多个监听器;它们都会在每次工具调用、提示词渲染与资源访问的前后被调用。相应的 McpCallContext 会在调用监听器时被注入。该对象含有实际发送给服务器的 MCP 消息,以及(如适用)一个 InvocationContext 实例(仅当此调用作为某次 AI 服务调用的一部分发生时)。

监听器可以逐个或批量添加:

java
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .addListener(new MyFirstListener())
    .addListener(new MySecondListener())
    .addListeners(List.of(new MyThirdListener(), new MyFourthListener()))
    .build();

资源

有两种处理资源的方式。要么应用可以调用 MCP 客户端的资源相关方法来以编程方式访问资源,要么有一个选项是通过合成工具(一个获取资源列表的工具、一个获取某资源内容的工具)把资源自动暴露给 LLM 调用,从而让 Chat 模型能自行查阅资源。

以编程方式访问资源

要获取服务器上的MCP 资源列表,使用 client.listResources(),或对于资源模板使用 client.listResourceTemplates()。这会返回一个 McpResource 对象列表(或分别为 McpResourceTemplate)。它们含有资源的元数据,最重要的是 URI。

要获取资源的实际内容,使用 client.readResource(uri),提供资源的 URI。这会返回一个 McpReadResourceResult,它含有一列 McpResourceContents 对象(单个 URI 上可能有多个资源内容,例如当该 URI 代表一个目录时)。每个 McpResourceContents 对象代表一个二进制块(McpBlobResourceContents)或文本(McpTextResourceContents)。

通过合成工具自动暴露资源

若在构建一个 McpToolProvider 时通过 builder 设置了一个 McpResourcesAsToolsPresenter 实例,MCP 工具提供者会在其 provideTools 方法的结果中自动添加两个合成工具,连同底层 MCP 服务器所支持的"常规"工具一起。一个工具用于获取资源列表,另一个用于获取某个特定资源。LangChain4j 提供了一个名为 DefaultMcpResourcesAsToolsPresenter 的默认实现来添加这两个工具:

注: 本节余下部分描述 DefaultMcpResourcesAsToolsPresenter。你可以接入一个行为不同的自有实现。

  • list_resources:列出由底层 MCP 服务器暴露的所有资源。此工具不带参数。
  • get_resource:读取某资源的内容。此工具带两个一起标识一个资源的参数: MCP 服务器名与 URI。

list_resources 的输出是一个像这样的 JSON 数组:

json
[ {
  "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"
} ]

此数组中的每个文档代表一个资源。每个资源由 uri 与 mcpServer 的组合标识,其中 mcpServer 是在创建期间赋给该 MCP 客户端的 key 的值(见 DefaultMcpClient.Builder#key)。当 Chat 模型调用 list_resources 工具时,它会收到这份资源列表,随后可决定调用 read_resource。list_resources 与 get_resource 工具的默认描述在多数情况下足以向 LLM 解释如何使用它们。不过,若你需要定制这些工具及其参数的描述,请用 DefaultMcpResourcesAsToolsPresenter.Builder 的方法来覆盖它们。

资源订阅

MCP 协议支持资源订阅,让客户端能在服务器上某个资源变化时收到通知。

旧协议(2025-11-25)

要订阅某个特定资源的更新,使用 client.subscribeToResource(uri)。当服务器更新该资源时,它会发送一个 notifications/resources/updated 通知。要处理这些通知,通过 onResourceUpdated builder 方法注册一个回调:

java
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .onResourceUpdated((client, uri) -> {
        // re-read the updated resource
        McpReadResourceResult result = client.readResource(uri);
        // process the updated contents...
    })
    .build();

// subscribe to a resource
mcpClient.subscribeToResource("file:///status");

// later, unsubscribe
mcpClient.unsubscribeFromResource("file:///status");

现代协议(2026-07-28)

在现代协议下,使用 subscribeToResources,它接受多个 URI 并返回一个订阅 ID:

java
long subId = mcpClient.subscribeToResources(List.of("file:///status", "file:///config"));
// later, unsubscribe using the subscription ID
mcpClient.unsubscribeFromResources(subId);

服务器必须先确认一个订阅,之后才能在其上发送任何东西,因此 subscribeToResources 会阻塞直到该确认到达。若服务器拒绝该订阅、婉拒所请求的 URI、或保持沉默,该调用会抛出异常,而不是返回一个永远不会交付任何东西的订阅 ID。用 resourcesTimeout 控制客户端等待确认多久:

java
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .resourcesTimeout(Duration.ofSeconds(10))  // default: 60 seconds
    .build();

由于该调用会阻塞,不要在某个 onResourceUpdated 回调内部调用它:那些回调运行在从服务器读取消息的那个线程上,而那正是必须交付确认的同一个线程。

对于列表变化通知(工具列表、提示词列表、资源列表变化),客户端默认会自动订阅。你可以通过 builder 标志来控制它:

java
McpClient mcpClient = DefaultMcpClient.builder()
    .transport(transport)
    .subscribeToToolListChanges(true)      // default: true
    .subscribeToPromptListChanges(true)    // default: true
    .subscribeToResourceListChanges(true)  // default: true
    .build();

提示词

要获取一个MCP 提示词列表,使用 client.listPrompts()。该方法返回一个 McpPrompt 的 List。一个 McpPrompt 含有关于该提示词的名称与参数的信息。

要渲染一个提示词的实际内容,使用 client.getPrompt(name, arguments)。一个被渲染的提示词可含一到多条消息,它们以 McpPromptMessage 对象表示。每个 McpPromptMessage 含有该消息的角色(user、assistant……)以及消息的实际内容。目前支持的消息内容类型有:McpTextContent、McpImageContent 与 McpEmbeddedResource。

你可以用 McpPromptMessage.toChatMessage() 把它转换为一个来自 LangChain4j 核心 API 的通用 dev.langchain4j.data.message.ChatMessage。但并非所有情况下都能做到。例如,若该提示词消息的 role 是 assistant 且它含有文本之外的内容,它会抛出一个异常。无论角色如何,把带二进制块内容的消息转换为 ChatMessage 都不受支持。

通过 Docker 使用 GitHub MCP 服务器

现在看看如何用 Model Context Protocol(MCP)以标准化方式把 AI 模型与外部工具桥接。下面的示例将通过 LangChain4j MCP 客户端与 GitHub 交互,从公共 GitHub 仓库拉取并总结最新的提交。为此无需重复造轮子,我们可以用现有的、在 MCP GitHub 仓库 中的 GitHub MCP 服务器实现。

思路是构建一个 Java 应用,连接到在 Docker 中本地运行的 GitHub MCP 服务器,以拉取并总结最新的提交。该示例使用 MCP 的 stdio 传输机制,在我们的 Java 应用与 GitHub MCP 服务器之间通信。

在 Docker 中打包并运行 GitHub MCP 服务器

要与 GitHub 交互,我们首先需要在 Docker 中搭建 GitHub MCP 服务器。GitHub MCP 服务器提供了一个通过 Model Context Protocol 与 GitHub 交互的标准接口。它支持文件操作、仓库管理与搜索功能。

要为我们的 GitHub MCP 服务器构建 Docker 镜像,你需要通过克隆仓库或下载代码,从 MCP 服务器 GitHub 仓库 获取代码。然后,进入根目录并执行以下 Docker 命令:

bash
docker build -t mcp/github -f src/github/Dockerfile .

Dockerfile 会搭建必要的环境并安装 GitHub MCP 服务器实现。一旦构建完成,该镜像会以 mcp/github 在本地可用。

bash
docker image ls

REPOSITORY   TAG         IMAGE ID        SIZE
mcp/github   latest      b141704170b1    173MB

开发工具提供者

让我们创建一个名为 McpGithubToolsExample 的 Java 类,用 LangChain4j 连接到我们的 GitHub MCP 服务器。这个类会:

  • 在一个 Docker 容器中启动 GitHub MCP 服务器(docker 命令在 /usr/local/bin/docker 中可用)
  • 使用 stdio 传输建立连接
  • 用一个 LLM 总结 LangChain4j GitHub 仓库最近 3 次提交

注:在下面的代码中,我们通过环境变量 GITHUB_PERSONAL_ACCESS_TOKEN 传入 GitHub 令牌。但对于公共仓库上一些无需认证的操作性而言,这是可选的。

这是实现:

java
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();
    }
}

NOTE

并非所有 LLM 都能同样好地支持工具。理解、选择并正确使用工具的能力,很大程度上取决于具体的模型及其能力。有些模型可能完全不支持工具,而另一些可能需要细致的提示词工程或额外的系统指令。

注:本示例使用 Docker,因此会执行一个在 /usr/local/bin/docker 中可用的 Docker 命令(请根据你的操作系统更改路径)。若你想用 Podman 代替 Docker,请相应地更改命令。

运行代码

要运行该示例,请确保 Docker 在你的系统上已启动并运行。另外,把你的 OpenAI API 密钥设置到环境变量 OPENAI_API_KEY 中。

然后运行该 Java 应用。你应该会得到一个总结 LangChain4j GitHub 仓库最近 3 次提交的响应,例如:

Here are the summaries of the last three commits in the LangChain4j GitHub repository:

1. **Commit [36951f9](https://github.com/langchain4j/langchain4j/commit/36951f9649c1beacd8b9fc2d910a2e23223e0d93)** (Date: 2025-02-05)
   - **Author:** Dmytro Liubarskyi
   - **Message:** Updated to `upload-pages-artifact@v3`.
   - **Details:** This commit updates the GitHub Action used for uploading pages artifacts to version 3.

2. **Commit [6fcd19f](https://github.com/langchain4j/langchain4j/commit/6fcd19f50c8393729a0878d6125b0bb1967ac055)** (Date: 2025-02-05)
   - **Author:** Dmytro Liubarskyi
   - **Message:** Updated to `checkout@v4`, `deploy-pages@v4`, and `upload-pages-artifact@v4`.
   - **Details:** This commit updates multiple GitHub Actions to their version 4.

3. **Commit [2e74049](https://github.com/langchain4j/langchain4j/commit/2e740495d2aa0f16ef1c05cfcc76f91aef6f6599)** (Date: 2025-02-05)
   - **Author:** Dmytro Liubarskyi
   - **Message:** Updated to `setup-node@v4` and `configure-pages@v4`.
   - **Details:** This commit updates the `setup-node` and `configure-pages` GitHub Actions to version 4.

All commits were made by the same author, Dmytro Liubarskyi, on the same day, focusing on updating various GitHub Actions to newer versions.

不用 AI 服务使用 MCP

前面的示例展示了如何用高层 AI 服务 API 使用 MCP。然而,也可以通过低层 API 使用 MCP。你可以手动使用你构建的 DefaultMcpClient 实例来针对服务器执行命令。一些例子:

java
// obtain a list of tools from the server
List<ToolSpecification> toolSpecifications = mcpClient.listTools();

// build and execute a ChatRequest that has access to the MCP tools
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 the LLM requested to invoke a tool, forward it to the MCP server
if(aiMessage.hasToolExecutionRequests()) {
    for (ToolExecutionRequest req : aiMessage.toolExecutionRequests()) {
        String resultString = mcpClient.executeTool(req);
        // prepare the result for adding it to the memory for the next ChatRequest...
        ToolExecutionResultMessage resultMessage = ToolExecutionResultMessage.from(req.id(), req.name(), resultString);
    }
}

若你想在聊天之外,直接用 MCP 客户端以编程方式执行一个工具,你需要手动构建一个 ToolExecutionRequest 实例:

java
// to execute a tool named "tool1" with argument "a=b"
ToolExecutionRequest request = ToolExecutionRequest.builder()
                .name("tool1")
                .arguments("{\"a\": \"b\"}")
                .build();
String toolResult = mcpClient.executeTool(request);

关于工具缓存的说明

DefaultMcpClient 维护着一个内部 MCP 工具缓存。一旦取回,除非服务器发送一个列表已更新的通知,否则会不再向 MCP 服务器请求工具列表。你可以通过调用 DefaultMcpClient.evictToolListCache() 手动清除此缓存。若你更愿意完全禁用缓存,请按如下配置客户端:

java
McpClient mcpClient = new DefaultMcpClient.Builder()
    .key("MyMCPClient")
    .transport(transport)
    .cacheToolList(false)
    .build();

MCP 注册表客户端

LangChain4j 还提供一个独立的客户端实现,能与 MCP 注册表 对话。目前,只实现了只读操作(你可以搜索 MCP 服务器,但不支持管理与添加服务器——请使用官方工具来做那些)。

警告: 发现并使用 MCP 服务器(尤其本地运行)可能带来严重的安全风险。在运行任何你在公共注册表中找到的 MCP 服务器之前,请确保你可以信任它。

该注册表客户端位于 dev.langchain4j.mcp.registryclient 包中,可以像这样初始化:

java
McpRegistryClient client = DefaultMcpRegistryClient.builder()
        .baseUrl("URL-OF-THE-REGISTRY")
        .build();

If no base URL is provided, the official registry will be used as the default (https://registry.modelcontextprotocol.io). Then, to search for MCP servers, use the registry.listServers(McpServerListRequest) method. A McpServerListRequest object can be built using the McpServerListRequest.Builder class. The Java API in LangChain4j closely mirrors the REST API of MCP registries as described in the official MCP Registry Reference.

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。