1. 为什么 Java 开发者需要 LangChain4j 接入 MCP 调用 GitHub 工具
如果你用 Java 写 AI 应用,大概率遇到过这个尴尬:模型能聊天,但一让它「查一下某个仓库最近的提交」就歇菜。原因很简单,大模型本身只有训练时冻结的知识,它没法主动去访问 GitHub 的实时数据。过去我们只能自己写一堆 HTTP 客户端、拼 REST 请求、解析 JSON,再手动塞回提示词里,代码又臭又长。
MCP(Model Context Protocol,模型上下文协议)就是来解决这个问题的。你可以把它理解成「AI 世界的 USB-C 接口」:只要外部工具按 MCP 标准暴露自己的能力,任何支持 MCP 的客户端都能即插即用。LangChain4j 作为 Java 生态里最成熟的 LLM 应用框架,已经原生支持 MCP,支持 stdio 和 HTTP(SSE) 两种通信方式。这意味着你不需要为每个工具单独写适配层,注册一个 MCP 工具提供者,模型就能自动发现并调用 GitHub 的查询、建 issue、读文件等能力。
这篇文章面向的是有 Java 基础、想把 AI 应用真正落地到工程里的开发者。我会带你走完一条完整链路:用 Docker 跑起 GitHub MCP Server,在 LangChain4j 里注册工具,配好 GitHub Token,最后发一次真实请求,让模型总结 LangChain4j 仓库最近三次提交。全程可复制,踩过的坑我也会标出来。
核心检索词先明确:LangChain4j MCP 接入 GitHub 工具链,本质是「Java AI 应用通过 MCP 协议调用外部工具」的落地实践。适合谁?适合正在做智能客服、代码助手、DevOps 自动化,又不想被 Python 绑死的 Java 团队。
在动手前,你需要准备三样东西:JDK 17+、Maven 或 Gradle、Docker。模型侧可以本地跑 Ollama,也可以走云端 API。我下面会用一个兼容 OpenAI 协议的网关来演示,这样你换模型时只改 Base URL 和 Model ID,代码不用动。
2. TaoToken 前置准备:拿到 Base URL、API Key 和 Model ID
LangChain4j 本身不绑定任何模型厂商,它通过ChatLanguageModel接口对接。实际项目里,我建议用一个统一的网关来管理模型调用,好处是切换模型、做额度控制、看调用日志都方便。这里我用 TaoToken 作为示例网关,它的接口兼容 OpenAI 协议,LangChain4j 的OpenAiChatModel可以直接对接。
你需要准备三件套,缺一不可:
第一,Base URL。TaoToken 的 API 地址是https://taotoken.net/api,注意这里不要加任何多余路径,LangChain4j 会自动拼接/v1/chat/completions。如果你用的是其他兼容网关,逻辑一样,把域名换成对应的即可。
第二,API Key。登录后在控制台的 API Keys 页面创建,格式通常是sk-开头的一串字符。这个 Key 只显示一次,创建后立刻复制保存。我试过忘记保存然后重新建,白白浪费一个额度。
第三,Model ID。这个取决于你想用哪个模型。比如你要用支持工具调用的模型,就填对应的模型名,像gpt-4o-mini、claude-3-5-sonnet这类。注意:MCP 工具调用要求模型本身支持 function calling / tool use,不是所有模型都行。如果你选的模型不支持工具调用,后面会看到模型「假装」调用了工具但实际没执行,这是最常见的坑之一。
把这三个值放进环境变量,别硬编码在代码里:
export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="sk-你的key" export GITHUB_PERSONAL_ACCESS_TOKEN="github_pat_你的token"GitHub Token 的获取路径是https://github.com/settings/personal-access-tokens/new,创建一个 fine-grained token,权限至少给public_repo的读权限。如果你只查公开仓库,其实不传 Token 也能跑,但 GitHub 有速率限制,传了更稳。
这里有个细节:LangChain4j 的OpenAiChatModel默认会去请求{baseUrl}/v1/chat/completions。如果你填的 Base URL 末尾带了/v1,就会变成/v1/v1/...导致 404。所以记住,Base URL 只填到域名加/api这一层。
另外,如果你打算长期跑编码类 Agent,可以了解下 Coding Plan,它针对高频代码场景做了额度优化;只是临时验证模型能力,用模型对话页面手动测几次就够了。这两个入口在 TaoToken 官网都能找到,按需选。
3. 可复制配置:Docker 启动 GitHub MCP Server 与 LangChain4j 工具注册
这一节是全文的核心,我给你两段可直接复制的配置:一段是 Docker 启动命令,一段是 LangChain4j 的 Java 代码。
先说 Docker。GitHub 官方提供了 MCP Server 的镜像,你可以直接拉取,也可以自己构建。自己构建的好处是版本可控:
git clone https://github.com/github/github-mcp-server.git cd github-mcp-server docker build -t mcp/github -f Dockerfile .构建完成后确认镜像存在:
docker image ls | grep mcp/github预期输出类似:
mcp/github latest b141704170b1 173MB然后启动容器。注意,MCP 的 stdio 模式要求容器以交互方式运行,所以-i参数不能少:
docker run --rm -d \ --name mcp-github-server \ -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PERSONAL_ACCESS_TOKEN \ mcp/github如果你用 stdio 模式让 LangChain4j 直接拉起容器,其实不需要提前docker run,客户端会用docker run -i自己启动子进程。两种方式选一种即可,我下面代码里用的是后者,更省事。
接下来是 Java 侧。先加依赖,Maven 的pom.xml:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>0.36.2</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-mcp</artifactId> <version>0.36.2</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>0.36.2</version> </dependency>然后是完整的工具注册代码。这段代码做了四件事:建模型、建 MCP 传输、建 MCP 客户端、把工具提供者绑到 AI 服务上。
import dev.langchain4j.mcp.McpToolProvider; import dev.langchain4j.mcp.client.DefaultMcpClient; import dev.langchain4j.mcp.client.McpClient; import dev.langchain4j.mcp.client.transport.McpTransport; import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.service.AiServices; import dev.langchain4j.service.tool.ToolProvider; import java.util.List; public class GithubMcpDemo { interface Bot { String chat(String message); } public static void main(String[] args) throws Exception { var model = OpenAiChatModel.builder() .baseUrl(System.getenv("TAOTOKEN_BASE_URL")) .apiKey(System.getenv("TAOTOKEN_API_KEY")) .modelName("gpt-4o-mini") .logRequests(true) .logResponses(true) .build(); McpTransport transport = new StdioMcpTransport.Builder() .command(List.of( "docker", "run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "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/langchain4j GitHub repository"); System.out.println("RESPONSE: " + response); } finally { mcpClient.close(); } } }几个关键点解释一下。StdioMcpTransport的command里,-e GITHUB_PERSONAL_ACCESS_TOKEN这种写法是把宿主机的环境变量透传进容器,不需要写=值,Docker 会自动读取当前 shell 的同名变量。logEvents(true)会打印 MCP 协议层的交互日志,调试时非常有用,生产环境可以关掉。
McpToolProvider.builder().failIfOneServerFails(false)是默认行为,意思是某个 MCP Server 挂了不影响其他 Server。如果你只有一个 Server 且希望它挂了就报错,可以设成true。
AiServices把toolProvider绑进去后,模型在对话时就能看到 GitHub MCP Server 暴露的所有工具,比如get_commit、list_commits、search_repositories等。模型会根据你的自然语言指令,自己决定调哪个工具、传什么参数。
4. 验证请求:一次完整的工具调用链路与预期返回
代码写完了,跑起来看结果。执行main方法,你会先看到一堆 MCP 协议日志,然后是模型请求日志,最后是响应。
先看 MCP 初始化阶段的日志,正常长这样:
MCP transport: starting process: docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN mcp/github MCP client: initialized, server info: name=github-mcp-server, version=0.1.0 MCP client: tools listed: [get_commit, list_commits, search_repositories, ...]看到tools listed就说明工具注册成功了。如果这一步卡住或者报错,多半是 Docker 没启动、镜像名写错、或者 Token 环境变量没传进去。
然后是模型请求日志,你会看到 LangChain4j 把工具定义一起发给了模型:
Request: { "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Summarize the last 3 commits..."}], "tools": [{"type": "function", "function": {"name": "list_commits", ...}}] }模型返回的第一次响应通常是一个tool_calls,表示它决定调用list_commits:
Response: { "choices": [{ "message": { "tool_calls": [{ "function": { "name": "list_commits", "arguments": "{\"owner\":\"langchain4j\",\"repo\":\"langchain4j\",\"per_page\":3}" } }] } }] }LangChain4j 收到这个后,会通过 MCP 客户端把调用转发给 GitHub MCP Server,Server 去请求 GitHub API,拿到结果再回传。最后模型基于工具返回的真实数据生成总结。
预期输出类似:
RESPONSE: 以下是 langchain4j/langchain4j 仓库最近三次提交的摘要: 1. 提交 36951f9(2025-02-05),作者 Dmytro Liubarskyi,更新 upload-pages-artifact 至 v3。 2. 提交 6fcd19f(2025-02-05),作者 Dmytro Liubarskyi,升级 checkout、deploy-pages 等 Action 至 v4。 3. 提交 2e74049(2025-02-05),作者 Dmytro Liubarskyi,更新 setup-node 和 configure-pages 至 v4。 这三次提交均由同一作者完成,主要内容是 GitHub Actions 版本升级。看到这个结果,说明整条链路通了:自然语言 → 模型决策 → MCP 工具调用 → GitHub API → 结果回传 → 模型总结。这就是 MCP 的价值,你只写了几十行 Java,就获得了一个能实时访问 GitHub 的 AI 助手。
如果你想验证其他工具,比如让模型「列出 langchain4j 仓库最近的 open issues」,模型会自动换成list_issues工具,参数也会相应变化。你可以多试几个指令,观察日志里工具名的变化。
5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth
这一节我按真实报错来整理,都是我在接入过程中实际撞过的。
错误一:401 Unauthorized
dev.langchain4j.exception.AuthenticationException: 401 Unauthorized原因通常是 API Key 没传对。检查TAOTOKEN_API_KEY环境变量是否真的被 Java 进程读到了。有个隐蔽的坑:如果你在 IDE 里配了环境变量,但用的是「Run」而不是「Debug」,某些 IDE 不会加载。最稳的办法是在代码里临时打印一下System.getenv("TAOTOKEN_API_KEY")的前几位确认。
另一个可能是 Base URL 写错了。如果你填了https://taotoken.net/api/v1,就会请求到/api/v1/v1/chat/completions,返回 404 而不是 401,但有些人会混淆。记住 Base URL 只到/api。
错误二:local proxy failed / Connection refused
java.net.ConnectException: Connection refused MCP transport: process exited with code 1这个多半是 Docker 没跑起来,或者docker命令不在 PATH 里。先在终端手动执行一遍docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN mcp/github,看能不能正常启动。如果报Cannot connect to the Docker daemon,说明 Docker Desktop 没开。
还有一种情况是 stdio 模式下,command列表里第一个参数写的是/usr/local/bin/docker,但你的 Docker 装在别的位置。用which docker确认实际路径,或者直接写docker让它走 PATH。
错误三:reading choices 相关空指针
java.lang.NullPointerException: Cannot invoke "java.util.List.get(int)" because "choices" is null这个报错说明模型返回的 JSON 里没有choices字段。常见原因是模型不支持工具调用,网关返回了一个错误结构,但 LangChain4j 按正常结构解析就炸了。解决办法是换一个明确支持 function calling 的模型。另外,有些网关在额度不足时也会返回非标准结构,检查一下账户余额。
错误四:OAuth / Token 权限不足
MCP tool call failed: 403 Forbidden Resource not accessible by personal access tokenGitHub Token 权限不够。fine-grained token 需要显式勾选仓库读取权限。如果你要访问私有仓库,还得把对应仓库加进 token 的授权列表。经典 token(classic)则要勾repo或public_repo。改完权限后,Token 不用重新生成,但容器要重启才能读到新权限。
错误五:工具调用死循环
有时候模型会反复调用同一个工具,日志里看到tool_calls出现好几次。这通常是模型能力问题,或者你的指令太模糊。解决办法是在AiServices里设置最大工具调用轮数,或者换一个工具调用能力更强的模型。LangChain4j 默认会限制轮数,但不同版本行为有差异,建议显式配置。
排查时记住一个原则:先看 MCP 日志确认工具注册成功,再看模型请求日志确认工具定义发出去了,最后看响应日志确认模型有没有返回tool_calls。三段日志一对照,问题基本定位。
6. 语义一致 CTA:把这条链路用到你的项目里
走到这里,你已经有了一个能跑通的 LangChain4j + MCP + GitHub 的最小闭环。接下来怎么用到实际项目?我给你三个方向。
第一,把它嵌进你的 CI/CD 流程。比如每次发版前,让 AI 自动总结本次提交、生成 changelog、甚至检查有没有遗漏的 issue 关联。你只需要把上面的Bot接口暴露成一个 HTTP 端点,用 Spring Boot 包一层就行。
第二,扩展更多 MCP Server。GitHub 只是其中一个,MCP 生态里还有文件系统、数据库、Slack、Notion 等 Server。LangChain4j 的McpToolProvider支持同时挂多个客户端,你可以让一个 AI 助手同时操作 GitHub 和本地文件。
第三,做代码审查助手。把 GitHub MCP 的get_pull_request、list_pull_request_files工具接进来,让模型自动读 PR diff 并给出审查意见。这个场景对 Java 团队特别实用。
如果你在接入过程中卡在 Key 或模型配置上,可以直接去 API Keys 页面重新生成一个,配合接入文档对照检查。想先手动验证模型是否支持工具调用,用模型对话页面发一句「调用 list_commits 查一下 langchain4j 仓库」就能看出来。长期跑编码类 Agent 的话,Coding Plan 在额度上更划算。
最后留一个实用技巧:把logRequests和logResponses在开发阶段打开,生产环境关掉,避免日志里泄露 Token 和业务数据。MCP 客户端的close()一定要放在finally里,否则 Docker 子进程会残留,跑几次就把内存吃满了。