Gemini CLI 接入 MCP 服务器实战:以 GitHub MCP Server 为例打通外部服务
本文以 Gemini CLI 官方教程 docs/cli/tutorials/mcp-setup.md 为主线,带你从零完成一次完整的 MCP(Model Context Protocol)服务器接入:准备 GitHub 凭据、在 settings.json 中通过 Docker 方式配置 GitHub MCP Server、用 /mcp 命令验证连接,并直接用自然语言驱动 GitHub 工具。读完本篇,你不仅能复现教程中的全部操作步骤,还能结合源码理解 Gemini CLI 的传输选择、超时控制、工具命名与状态机等底层机制,从而具备排查任意 MCP 服务器连接问题的能力。
一、MCP 服务器是什么,为什么需要它
MCP 服务器是一个通过 Model Context Protocol 向 Gemini CLI 暴露工具(tools)、提示词(prompts)和资源(resources)的应用。它相当于模型与外部世界之间的桥梁:
- 发现工具:通过标准化的 JSON Schema 定义列出可用工具及其参数;
- 执行工具:用既定参数调用工具并拿到结构化响应;
- 访问资源:读取服务器暴露的 URI 数据(文件、API 载荷、报告等)。
借助 MCP,Gemini CLI 的能力可以延伸到内置工具之外——例如操作 GitHub 仓库、查询数据库、调用任意 API。本教程选择 GitHub MCP Server 作为示例,因为它同时覆盖了三个最常见诉求:Docker 容器化部署、PAT 鉴权、自然语言驱动的外部写操作(创建 Issue、读取 PR)。
二、前置条件
在开始之前,确认以下环境已就绪:
- 已安装 Gemini CLI(可执行
gemini命令进入交互式界面); - Docker:本教程的 GitHub MCP Server 以 Docker 容器方式运行,因此宿主机必须安装并正在运行 Docker;
- GitHub 个人访问令牌(PAT):一个具备 repo 权限的 PAT,具体权限范围见下一节。
三、准备凭据:创建 GitHub PAT
大多数 MCP 服务器都需要鉴权,GitHub 使用 PAT。按以下步骤创建:
- 在 GitHub 的 Fine-grained personal access tokens 页面创建一个细粒度 PAT(fine-grained PAT);
- 权限授予建议(最小化原则):
- Metadata 与 Contents:只读(Read);
- Issues 与 Pull Requests:读写(Read/Write),本教程的"创建 Issue"场景依赖写权限;
- 将令牌存入环境变量,而不是写进配置文件:
macOS/Linux
export GITHUB_PERSONAL_ACCESS_TOKEN="github_pat_..."
Windows (PowerShell)
$env:GITHUB_PERSONAL_ACCESS_TOKEN="github_pat_..."
说明:这个环境变量名
GITHUB_PERSONAL_ACCESS_TOKEN是 GitHub MCP Server 官方约定的变量名,Docker 容器内部通过它读取令牌。你只需保证宿主机环境里有这个名字的变量即可。
四、配置 Gemini CLI:mcpServers 块详解
Gemini CLI 通过在 settings.json 中声明 mcpServers 对象来获知有哪些 MCP 服务器。每个条目本质上是一条指令:"启动这个命令,然后跟它的标准输入/输出对话。"
4.1 完整配置示例(Docker + GitHub MCP Server)
打开 ~/.gemini/settings.json(用户级)或项目根目录下的 .gemini/settings.json(项目级),加入如下 mcpServers 块:
{
"mcpServers": {
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:latest"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
}
}
}
}
逐字段解读:
| 字段 | 作用 |
|---|---|
"command": "docker" |
要启动的可执行命令,这里是 docker,说明该服务器走 Stdio 传输 |
"args": [...] |
传给 docker 的参数:-i 保持 stdin 打开(Stdio 传输必需),--rm 退出后清理容器,-e GITHUB_PERSONAL_ACCESS_TOKEN 把宿主机同名变量注入容器 |
"env": { ... } |
通过 ${GITHUB_PERSONAL_ACCESS_TOKEN} 在运行时从宿主机环境展开令牌值,避免把密钥硬编码进配置文件 |
服务器名 "github" |
即配置键名,会进入工具的全限定名 mcp_github_*,不要使用下划线命名服务器(见第六节) |
关键设计点:令牌不落盘。env 中的 ${VAR} 语法由 Gemini CLI 的环境变量展开器在启动服务器时解析。这一点在源码中有明确实现——packages/core/src/tools/mcp-client.ts 中通过 expandEnvVars 对 env 值做变量展开,支持 POSIX 语法 $VAR / ${VAR}(全平台)与 Windows 语法 %VAR%。
4.2 环境变量展开与环境净化(源码级补充)
从源码结构看,Gemini CLI 在启动 MCP 服务器进程时并非把宿主机的整个环境直接透传。packages/core/src/tools/mcp-client.ts 引用了 sanitizeEnvironment 做环境净化:像 GEMINI_API_KEY、GOOGLE_API_KEY 这类核心密钥,以及匹配 *TOKEN*、*SECRET*、*PASSWORD*、*KEY*、*AUTH* 等模式的变量会被默认从基础环境中剔除,防止第三方 MCP 服务器意外读到你的敏感凭据。只有你在 env 中显式声明的变量才会传递给服务器——这正是本教程"先 export 再用 ${GITHUB_PERSONAL_ACCESS_TOKEN} 显式引用"写法的底层原因:显式声明即视为用户知情同意,会跳过自动脱敏。
4.3 替代方案:用 gemini mcp add 命令写入配置
除了手编 JSON,仓库源码(packages/cli/src/commands/mcp/add.ts)表明 Gemini CLI 提供了 gemini mcp add 子命令,等效地生成 mcpServers 条目:
# stdio 传输(默认):command + args + env
gemini mcp add github docker \
-e GITHUB_PERSONAL_ACCESS_TOKEN='${GITHUB_PERSONAL_ACCESS_TOKEN}' \
-- run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server:latest
# 可选参数(来自 yargs 定义):
# --scope <user|project> 写入用户级还是项目级 settings(默认 project)
# --transport <stdio|sse|http> 传输类型(默认 stdio)
# --header "K: V" sse/http 传输的自定义请求头
# --timeout <ms> 连接超时
# --trust 跳过所有工具调用确认
# --include-tools / --exclude-tools 工具白/黑名单
注意 -- 之后的参数会原样作为服务器参数追加(源码中通过 populate-- 与 middleware 合并实现)。对于本教程的 Docker 场景,由于参数较多且含 -e 注入,手编 settings.json 的可读性更好,两种方式的最终产物完全一致。
五、验证连接:/mcp 命令族
重启 Gemini CLI 后,它会按配置自动启动并发现 MCP 服务器。在交互界面输入:
/mcp list
连接成功时应看到类似 ✓ github: docker ... - Connected 的状态输出。如果显示 Disconnected 或错误信息,先确认 Docker 守护进程在运行、PAT 有效。
从 packages/cli/src/ui/commands/mcpCommand.ts 可以看到,/mcp 实际是一个子命令集合,日常排障时都很有用:
| 命令 | 作用 |
|---|---|
/mcp 或 /mcp list |
列出全部服务器、连接状态、各服务器的工具/Prompt/资源、鉴权状态与错误信息;后面跟文本则作为服务器名过滤 |
/mcp desc |
在 list 基础上附带工具描述 |
/mcp schema |
进一步附带参数 Schema |
/mcp reload(别名 refresh) |
调用 McpClientManager.restart() 重启所有 MCP 服务器并重新发现工具,随后自动再执行一次 list |
/mcp auth [name] |
对需要 OAuth 的远程服务器发起认证(本教程的 Docker 方案不需要) |
/mcp enable <name> [--session] / /mcp disable <name> [--session] |
启用/禁用某服务器,受 mcp.allowed / mcp.excluded 全局策略约束,改完自动重启服务器 |
/mcp list 输出的"错误信息"字段来自 McpClientManager.getLastError(serverName)——即每个服务器最近一次连接/发现失败的原始报错,是排障的第一现场。
六、底层原理:Gemini CLI 如何连接并使用一个 MCP 服务器
理解这一节能让你在 Disconnected 时快速定位问题层。
6.1 传输选择逻辑
packages/core/src/tools/mcp-client.ts 的 connectToMcpServer() 按配置字段决定传输方式:
- 配了
command→StdioClientTransport:spawn 子进程(本教程的 Docker 场景),走 stdin/stdout; - 配了
url→ 先按 Streamable HTTP 尝试,失败则回退SSEClientTransport(源码中对 HTTP 404/401 有明确的回退与 OAuth 分支); - 配了
httpUrl→ 直接StreamableHTTPClientTransport。
默认请求超时为 MCP_DEFAULT_TIMEOUT_MSEC = 10 * 60 * 1000(10 分钟),可在每个服务器配置中用 timeout 覆盖。
6.2 状态机与发现流程
每个服务器有一个 MCPServerStatus 状态机:DISCONNECTED → CONNECTING → CONNECTED(失败回落到 DISCONNECTED,另有 BLOCKED / DISABLED 两个管理态)。连接成功后 McpClient.discoverInto() 依次做三件事:拉取 prompts、发现 tools、发现 resources,并注册进对应的全局注册表;如果三者全空,会抛出 "No prompts, tools, or resources found" 并断开。此外客户端会监听 tools/list_changed 等通知,服务器端工具变化时无需手动 /mcp reload 也能自动刷新(源码中带合批与 500ms 重试逻辑,防止服务器"先通知后就绪"的竞态)。
6.3 工具命名:为什么叫 mcp_github_list_pull_requests
所有 MCP 工具都会无条件加上完全限定名(FQN)前缀,格式为 mcp_{serverName}_{toolName}。这在 packages/core/src/tools/mcp-tool.ts(约 L595)中实现,并有 packages/core/src/tools/tool-registry.test.ts 等测试用例反复断言 mcp_${serverName}_${toolName} 的命名结果。因此教程中 Agent 调用的 mcp_github_list_pull_requests,正是"服务器名 github + 原始工具名 list_pull_requests"拼接而来。
由此产生一条重要的命名约束(与 Policy Engine 相关):服务器名不要含下划线。策略解析器按 mcp_ 之后的第一个下划线切分 FQN,my_server 这类命名会让通配规则静默失效。
6.4 工具调用与确认
模型选中某个 MCP 工具后,由 DiscoveredMCPTool 处理确认逻辑:配置里 trust: true 的服务器跳过所有确认;否则弹出对话框,可选"仅本次"、"始终允许该工具"、"始终允许该服务器"或取消。执行时用原始工具名调用服务器,返回结果拆成两部分:llmContent 供模型上下文使用,returnDisplay 以 Markdown 形式展示给用户。
七、使用新工具:自然语言驱动 GitHub
服务器连上后,Agent 就多了"GitHub 能力",无需学习任何特殊命令,直接用自然语言下达即可。
场景 1:列出 Pull Requests
Prompt:
List the open PRs in the google/gemini-cli repository.
Agent 的行为链条:
- 识别出请求匹配 GitHub 工具;
- 调用
mcp_github_list_pull_requests; - 把返回数据整理后呈现给你。
场景 2:创建 Issue
Prompt:
Create an issue in my repo titled "Bug: Login fails" with the description "See logs".
由于 PAT 授予了 Issues 的 Read/Write 权限,这一步会真正在仓库中创建 Issue——这也是最小化权限的重要示范:不需要给仓库完整的 admin 权限,只给实际用到的 Issues/PR 写权限。
八、故障排查(Troubleshooting)
| 症状 | 处理方法 |
|---|---|
| 服务器起不来 | 在终端手动执行 settings.json 里那条 docker 命令,看是否输出 "image not found" 等错误;确认 Docker 守护进程在跑、镜像可拉取 |
| 工具找不到 | 执行 /mcp reload(即 refresh)强制 CLI 重新向服务器查询其能力,源码层面等价于 mcpClientManager.restart() + geminiClient.setTools() + 斜杠命令重载 |
| 状态是 Disconnected 且 list 里有报错 | /mcp list 的 error 字段会给出最近一次连接失败的原始错误;配合 --debug 启动 CLI(交互模式按 F12 打开调试控制台)查看传输层日志 |
| 工具列表比预期少 | 检查是否被 includeTools / excludeTools 过滤(excludeTools 优先),或全局 mcp.allowed / mcp.excluded 名单,或服务器被 /mcp disable 禁用 |
| 凭据未生效 | 确认环境变量已 export 到启动 CLI 的同一个 shell;env 中的 ${VAR} 在变量缺失时会展开为空字符串 |
补充一条来自源码的行为细节:服务器进程若不提供任何 tools/prompts/resources,连接会在发现阶段被关闭,状态回落 DISCONNECTED——所以"连上了但什么工具都没有"通常意味着服务器本身没注册工具,或注册名被过滤规则挡掉了。
九、总结与延伸阅读
回顾本教程完成的完整闭环:
- 凭据:细粒度 PAT + 宿主机环境变量,令牌不写入配置文件;
- 配置:
settings.json中mcpServers.github声明 Docker 启动命令与${VAR}环境展开; - 验证:重启 CLI 后
/mcp list确认Connected,失败时看 error 字段并手动跑 docker 命令; - 使用:自然语言触发
mcp_github_list_pull_requests等 FQN 工具; - 维护:
/mcp reload、/mcp enable/disable管理服务器生命周期。
如果你想继续深入,建议阅读以下仓库内的配套文档:
- MCP 服务器完整参考:三种传输(Stdio/SSE/Streamable HTTP)、OAuth 自动发现、
authProviderType各取值、headers、工具过滤、Schema 清洗与发现流程的深度剖析,以及远程服务器配置示例; - MCP 资源工具:用
@server://...引用远程资源; - 设置项参考:
mcp.allowed/mcp.excluded等全局开关的完整说明; - 核心实现源码:mcp-client.ts(连接、传输选择、状态机、通知刷新)、mcp-tool.ts(工具包装与 FQN 命名)、mcpCommand.ts(
/mcp命令族)、mcp add 命令。
掌握这条"配置 → 发现 → 命名 → 确认 → 执行"的主线后,接入 Slack、Postgres、Google Drive 等任意 MCP 服务器都只是替换 command / url 与 env 的差异,其余流程完全一致。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00