🧩 先搞懂:什么是 MCP? MCP(Model Context Protocol)是 Anthropic 提出的一个开放标准协议,目的是让 LLM 能够以一种统一的方式连接外部工具和数据源。打个比方:以前每个工具都要写一套适配代码(像给每种插座买不同转换器),MCP 就是推出了一个"万能插座"——只要服务器实现了 MCP 协议,任何支持 MCP 的客户端都能自动发现并使用它的工具。

核心结论一句话 创建一个 McpTransport → 搭建 McpClient → 生成 McpToolProvider → 通过 .toolProvider() 挂到 AI Service 上,LLM 就能调用远程服务器上暴露的所有工具和资源。


📋 文章结构总览

主题 作用
MCP 传输层 stdio / Streamable HTTP / 旧版 HTTP+SSE
MCP 客户端 管理与服务器的连接
MCP 工具提供者 将 MCP 工具桥接到 AI Service
工具过滤 只暴露需要的工具,减少幻觉风险
日志 MCP 协议的日志消息处理
资源 (Resources) 读取外部数据源(编程式 + 合成工具)
提示词 (Prompts) 使用 MCP 服务器预定义的提示词模板
Docker + GitHub 实战示例:用 MCP 操作 GitHub 仓库
直接调用 MCP 绕过 AI Service,手动执行 MCP 工具
工具缓存 默认缓存工具列表,可手动刷新或禁用

一、MCP 传输层 —— 三种连接方式

MCP 规定了客户端和服务器之间的通信通道,LangChain4j 全部支持:

方式一:stdio(本地子进程) ⭐ 最常用

McpTransport transport = new StdioMcpTransport.Builder()
    .command(List.of("/usr/bin/npm", "exec", "@modelcontextprotocol/server-everything@0.6.2"))
    .logEvents(true)  // 在日志中打印通信流量
    .build();

把 MCP 服务器当作本地命令启动,通过 stdin/stdout 通信。适合开发调试和本地工具。

方式二:Streamable HTTP(可流式 HTTP) ⭐ 生产推荐

McpTransport transport = new StreamableHttpMcpTransport.Builder()
    .url("http://localhost:3001/mcp")
    .logRequests(true)   // 打印请求
    .logResponses(true)  // 打印响应
    .build();

标准的 HTTP 长连接,客户端发 POST 请求,服务器可以通过 SSE 流持续推送多个响应。

方式三:旧版 HTTP+SSE(已弃用) ⚠️

McpTransport transport = new HttpMcpTransport.Builder()
    .sseUrl("http://localhost:3001/sse")
    .logRequests(true)
    .logResponses(true)
    .build();

需要两个 URL(SSE 端点 + POST 端点)。已被官方弃用,未来会移除。新项目不要用这种方式。

注意:Streamable HTTP 目前不会创建全局 SSE 流,依赖"服务器主动发起通知"的功能可能无法工作。如果服务器把通知附在客户端操作的 SSE 流里返回,则正常工作。


二、三步创建 MCP 工具提供者

第 1 步:创建 MCP 客户端

McpClient mcpClient = new DefaultMcpClient.Builder()
    .key("MyMCPClient")        // ← 可选但推荐,多客户端时用于区分
    .transport(transport)
    .build();

第 2 步:创建工具提供者

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

第 3 步:绑定到 AI Service

Bot bot = AiServices.builder(Bot.class)
    .chatModel(model)
    .toolProvider(toolProvider)  // ← 关键:把 MCP 工具注入进来
    .build();

也可以直接提供工具映射:

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

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

三、工具过滤 —— 只暴露需要的工具

MCP 服务器可能暴露几十个工具,但你只需要其中几个。用过滤器可以减少 token 消耗和幻觉风险:

按名称过滤:

McpToolProvider toolProvider = McpToolProvider.builder()
    .mcpClients(mcpClient)
    .filterToolNames("get_issue", "get_issue_comments", "list_issues")  // 只看 issue
    .build();

这样 AI 服务只能用这 3 个工具来读取 issue 和评论,不能创建新 issue。

按自定义逻辑过滤:

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

同一个 builder 上多次调用 filter 会形成逻辑 AND 关系。运行时还可以动态增删客户端和过滤器。

容错策略:

.failIfOneServerFails(false)  // 默认:忽略某个服务器的错误,继续其他
.failIfOneServerFails(true)   // 任意服务器出错就抛异常

四、日志

MCP 协议定义了服务器向客户端发送日志消息的机制。默认转为 SLF4J 日志,自定义只需实现接口:

McpClient mcpClient = new DefaultMcpClient.Builder()
    .transport(transport)
    .logMessageHandler(new MyLogMessageHandler())  // ← 自定义日志处理
    .build();

五、资源 (Resources) —— 让 LLM 读取外部数据

资源有两种使用方式:

方式一:编程式访问(你手动调)

// 列出所有资源
List<McpResource> resources = client.listResources();

// 列出资源模板(带 URI 参数的资源)
List<McpResourceTemplate> templates = client.listResourceTemplates();

// 读取指定资源内容
McpReadResourceResult result = client.readResource("file:///data/config.json");
// result.getResources() 返回 McpResourceContents 列表
// 可能是文本 (McpTextResourceContents) 或二进制 (McpBlobResourceContents)

方式二:自动暴露为合成工具(让 LLM 自己调)

构建 McpToolProvider 时设置 McpResourcesAsToolsPresenter

var presenter = new DefaultMcpResourcesAsToolsPresenter.Builder()
    // 可以自定义合成工具的描述文案
    .build();

McpToolProvider toolProvider = McpToolProvider.builder()
    .mcpClients(mcpClient)
    .resourcesAsTools(presenter)  // ← 开启
    .build();

会自动增加两个合成工具:

  • list_resources — 列出所有可用资源
  • get_resource — 读取指定资源内容(需要 MCP 服务器名 + URI)

每个资源由 (mcpServer, uri) 唯一标识。


六、提示词 (Prompts) —— 复用服务器预定义的模板

MCP 服务器可以提供预定义的提示词模板:

// 获取所有可用的提示词
List<McpPrompt> prompts = client.listPrompts();

// 渲染并执行提示词
McpPromptMessage prompt = client.getPrompts("summarize_repo", Map.of("repo", "langchain4j"));

// 转换为 ChatMessage 供 LLM 使用
ChatMessage chatMessage = prompt.toChatMessage();

提示词内容支持多种类型:McpTextContentMcpImageContentMcpEmbeddedResource

限制:如果角色是 assistant 且内容不是文本 → 抛出异常;含二进制内容 → 无法转换。


七、实战:通过 Docker 使用 GitHub MCP 服务器

用一个完整的例子串起来:让 GPT 总结 LangChain4j 仓库最近 3 次提交。

前置准备:

# 1. 打包 GitHub MCP 服务器
docker build -t mcp/github -f src/github/Dockerfile .

# 2. 确认镜像存在
docker image ls
# REPOSITORY    TAG       IMAGE ID      SIZE
# mcp/github    latest    b141704170b1  173MB

完整代码:

public static void main(String[] args) throws Exception {

    // 1. 配置 LLM
    ChatModel model = OpenAiChatModel.builder()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("gpt-4o-mini")
        .logRequests(true)
        .logResponses(true)
        .build();

    // 2. 通过 Docker 运行 GitHub MCP 服务器(stdio 传输)
    McpTransport transport = new StdioMcpTransport.Builder()
        .command(List.of("/usr/local/bin/docker", "run", "-e", 
            "GITHUB_PERSONAL_ACCESS_TOKEN", "-i", "mcp/github"))
        .logEvents(true)
        .build();

    // 3. 创建 MCP 客户端
    McpClient mcpClient = new DefaultMcpClient.Builder()
        .key("github-mcp")
        .transport(transport)
        .build();

    // 4. 创建工具提供者
    ToolProvider toolProvider = McpToolProvider.builder()
        .mcpClients(List.of(mcpClient))
        .build();

    // 5. 绑定到 AI Service
    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();  // 记得关闭!
    }
}

输出效果:

RESPONSE: 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`

八、不使用 AI Service 直接调用 MCP

除了高层 API,也可以用底层 API 手动控制:

// 1. 列出 MCP 服务器上的所有工具
List<ToolSpecification> toolSpecifications = mcpClient.listTools();

// 2. 构造请求,带上这些工具
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();

// 3. 如果 LLM 要调用工具,手动执行
if (aiMessage.hasToolExecutionRequests()) {
    for (ToolExecutionRequest req : aiMessage.toolExecutionRequests()) {
        String resultString = mcpClient.executeTool(req);
        ToolExecutionResultMessage resultMessage = 
            ToolExecutionResultMessage.from(req.id(), req.name(), resultString);
        // 把结果喂回给 LLM...
    }
}

直接执行某个工具:

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

🎯 面试高频追问

Q1:MCP 和我们之前学的 @Tool 有什么区别?

答:@Tool 是你自己在 Java 代码里定义的工具方法,由 LangChain4j 本地执行;MCP 则是通过一个标准化协议连接到外部 MCP 服务器(可以是 Node.js/Python/Go 写的独立服务),工具由远端服务器提供和执行。MCP 本质上是工具的"跨语言、跨进程分发协议"。

Q2:为什么 MCP 被称为"USB-C 之于 AI"?

答:就像 USB-C 统一了各种外设的连接标准一样,MCP 试图统一 LLM 接入外部工具的协议。以前每个工具都要单独写适配器,现在只要服务端实现 MCP 协议,任何 MCP 客户端都能自动发现和使用它。Anthropic 提出这个协议的目的就是成为 AI 时代的通用接口标准。

Q3:stdio 和 Streamable HTTP 两种传输方式怎么选?

答:开发调试阶段用 stdio 最简单——直接把 MCP 服务器当本地命令启动就行。生产环境推荐 Streamable HTTP——更稳定、更适合分布式部署,客户端和服务端通过网络通信而非父子进程。

Q4:多个 MCP 服务器同时使用时要注意什么?

答:① 给每个客户端设唯一的 key;② 用 filterToolNames 或自定义 BiPredicate 过滤掉冲突或不需要的工具;③ 设置 failIfOneServerFails 决定容错策略;④ 注意工具总数不能超过 LLM 的 context window。

Q5:资源和工具有什么区别?

答:工具是可以执行操作的(比如创建 issue、发送邮件),资源是被动读取的数据(比如文件内容、数据库记录)。MCP 把它们分开设计,资源通过 list_resources / get_resource 两个合成工具暴露给 LLM,而工具则正常注册到工具列表中。


✅ 总结

MCP 是 LangChain4j 连接外部世界的桥梁:

  • 传输层三路并行:stdio(开发首选)/ Streamable HTTP(生产推荐)/ 旧版 HTTP+SSE(已弃用)
  • 三步上手:Transport → Client → ToolProvider → 挂到 AI Service
  • 工具过滤:按名称或自定义逻辑筛选,减少幻觉和 token 浪费
  • 容错策略:单服务器故障可忽略也可报错,灵活可控
  • 资源系统:编程式读取 + 合成工具自动暴露,让 LLM 自主拉取数据
  • 提示词模板:复用 MCP 服务器预定义的 Prompt 模板
  • Docker 实战:一行命令启动 GitHub MCP 服务器,让 LLM 直接操作 Git 仓库
  • 低层 API:不依赖 AI Service 也能手动列出、执行 MCP 工具
  • 工具缓存:默认缓存提升性能,支持手动刷新或完全禁用
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐