关于 GitHub (read-only)
GitHub (read-only) 是一个只读 Python MCP 服务,让客户端查询 GitHub 仓库、议题、拉取请求和提交。项目还包含用于评估工具选择与参数的基准测试框架。
主要功能
- 01提供 6 个只读 GitHub 工具
- 02使用细粒度只读令牌
- 03支持重试、速率限制处理和 ETag 缓存
- 04包含离线假 API 基准测试
- 05评估并优化工具描述
适用场景
- 01查询仓库、议题、拉取请求和提交
- 02测量模型的工具选择与参数准确性
- 03在离线环境运行 MCP 基准测试
MCP 相关
该服务通过 stdio 上的 MCP 暴露只读 GitHub 工具。另含可用于其他 stdio MCP 服务的基准测试工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
uvx github-mcp-readonlyGITHUB_TOKEN密钥Fine-grained GitHub token with read-only access (Metadata, Contents, Issues, Pull requests).GITHUB_MCP_TOOLSETTool descriptions to serve: v2 (default, optimized), v1 or v1b (benchmark baselines).本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
GitHub MCP Server:教 AI 选择正确的工具
一个只读的 Model Context Protocol 服务,让 Claude(或任何 MCP 客户端)能够回答有关我的 GitHub 仓库、议题、拉取请求和提交的问题。它还附带一个基准测试,用于衡量模型选择正确工具并提供正确参数的频率。
重写工具描述后,一个本地 7B 模型的正确率从 63% 提升到 95%,在从未用于调优的留出问题集上也从 60% 提升到 90%。 消融实验展示了这 32 个百分点的提升从何而来,其中大部分并非来自文案:
| 改动 | 提升百分点 | 内容 |
|---|---|---|
接受不带所有者的仓库名称(mcp-bench-app,而不只是 owner/mcp-bench-app) | +17 | 服务中的输入处理 |
在 limit 参数描述中写明范围 (1-50) | +8 | 一句参数说明 |
| 重写工具描述(何时使用、何时不使用,以及替代方案) | +7 | 工具措辞 |
v2 中留下的每个错误,都是模型请求了 API 不支持的内容:已合并的 PR、按星标排序,或通过开放议题工具查找已关闭议题。更好的措辞无法解决这些问题。下一步是在架构中支持这些功能。

我构建的内容
- MCP 服务(Python,
mcpSDK 2.x),通过标准输入输出提供 6 个只读工具:list_repositories、get_repository、list_open_issues、search_issues、list_pull_requests、get_recent_commits。 - 最小权限访问: 使用细粒度的只读 GitHub 令牌和
readOnlyHint注释。 令牌保留在服务进程中,不会传给模型。 - 生产环境行为: 输入经过架构验证,发生错误时采用带退避和抖动的重试,处理主要和次要速率限制,使用 ETag 缓存(304 响应不会计入速率限制),列表工具返回精确总数,并提供能告知模型如何恢复的错误消息。
- 包含标准答案的基准测试: 种子脚本创建两个仓库,包含 15 个议题、5 个拉取请求,以及两位作者的提交。模拟 GitHub API 可离线提供相同数据,因此基准测试也能在无需令牌的情况下于 CI 中运行。
- 三项评估: 带参数评分的工具选择(60 个问题)、留出集(20 个问题),以及根据种子事实检查最终答案的多步 Agent 运行(13 个)。
- 消融实验(
v1 → v1b → v2),用于区分输入处理与描述措辞的影响。 - 自动描述优化器,根据错误重写描述,并且仅在留出集准确率不下降时保留改动。
- 可用于评测任何 MCP 服务的测试框架,不局限于此项目(examples/)。
- 结果仪表板(docs/),包含运行过程中的每个问题、调用和错误。
- 测试与 CI,支持 Python 3.10–3.13(ruff、mypy、pytest,以及一项检查:当调用正确时,测试框架能否获得 100% 分数)。
架构
flowchart LR
U[User] -->|question| C["Claude Desktop<br/>(MCP client)"]
C <-->|"JSON-RPC over stdio<br/>tools/list · tools/call"| S["github-mcp<br/>Python MCP server"]
S -->|"HTTPS · read-only token<br/>ETags · retries"| G[(GitHub REST API)]
E["eval/run_eval.py"] -.->|"same MCP protocol"| S
E -.->|"question + tool schemas"| L["LLM under test<br/>(Ollama or Claude)"]
S -.->|"GITHUB_API_URL (CI)"| F[("fake GitHub<br/>seeded repos")]
| 层 | 职责 |
|---|---|
server.py | 注册工具、验证参数,返回带精确总数的紧凑 JSON |
descriptions.py | 版本化描述(v1 基线、v1b 消融、v2 优化版,或 JSON 文件) |
github_client.py | 身份验证、分页、重试、速率限制、ETag 缓存、类型化错误 |
eval/ | 基准测试运行器、参数评分器、种子脚本、模拟 GitHub、优化器、图表、仪表板导出 |
评估
方法。 每个问题都会连同服务自身的工具列表一起发送给模型,这与 Claude Desktop 发送的内容完全一致。测试框架通过 MCP 执行选中的调用并评分:
- ✅ 正确:预期工具,调用成功,参数匹配(例如,“最近 3 天” →
since_days=3) - 🟡 参数错误:工具正确,调用成功,但参数错误或缺失
- ⚠️ 工具错误:使用了其他工具,或未使用工具
- ❌ 工具调用失败:工具正确,但调用返回错误
共有 60 个问题(每个工具 10 个,其中三分之一刻意选自工具之间的重叠场景),另有 20 个留出问题,这些问题是在 v2 冻结后编写的,从未用于调优。数据来自真实 GitHub 上的预置基准仓库;模型通过 Ollama 在本地运行,温度设为 0,每种配置运行 3 次,报告中位数。在主测试集上,3 次运行的结果最多相差 2 个百分点;在留出集上最多相差 5 个百分点。
| 配置(qwen2.5:7b) | 正确 | 参数错误 | 工具错误 | 工具调用失败 | 输入 token 数 |
|---|---|---|---|---|---|
| v1:描述含糊 | 63% | 0% | 8% | 28% | 614 |
| v1b:v1 + 裸仓库名 | 80% | 0% | 8% | 12% | 614 |
| v2:重写描述 | 95% | 0% | 2% | 3% | 1533 |
| v1 在留出问题上的表现 | 60% | 5% | 15% | 20% | 613 |
| v2 在留出问题上的表现 | 90% | 0% | 5% | 5% | 1532 |
使用伪造 GitHub 进行的离线测试结果也相同(65% / 82% / 95%)。
第二个模型 llama3.1:8b,问题和设置相同:
| 配置(llama3.1:8b) | 正确 | 参数错误 | 工具错误 | 工具调用失败 | 输入 token 数 |
|---|---|---|---|---|---|
| v1:描述含糊 | 32% | 7% | 12% | 50% | 643 |
| v1b:v1 + 裸仓库名 | 47% | 7% | 12% | 35% | 643 |
| v2:重写描述 | 92% | 5% | 2% | 2% | 1559 |
| v2 在留出问题上的表现 | 95% | 0% | 0% | 5% | 1558 |
即使在 v1 上,Llama 也有 88% 的时间选对了工具。它的问题出在参数上:它反复在上限为 50 的工具中请求 limit=100,还会在预期裸名称时传入 owner/repo。对 qwen 有帮助的改动(裸名称、明确说明限制范围)对 llama 的帮助更大,因此这一经验适用于不同模型:应明确写出参数约束,而不只是说明工具的功能。
Agent 模式(13 个多步骤问题,答案根据预置数据进行检查):v1 62%,v2 69%。13 个问题中,一个答案就相当于 8 个百分点,因此这一差异处于噪声范围内(离线测试的结果恰好相反)。
更好描述的代价: v2 会为每个请求增加约 920 个输入 token(614 → 1533)。
原始结果,每次运行的每个问题对应一行 JSON:eval/results/。
对你自己的 MCP 服务进行基准测试
该测试框架不限于此服务。只需提供启动任意 stdio MCP 服务的命令,以及一个包含问题的 JSONL 文件:
python eval/run_eval.py --server "npx -y @modelcontextprotocol/server-filesystem ." \
--questions my_questions.jsonl --domain "the user's files" --backend ollama --runs 3
examples/ 中有一个小型笔记服务,包含 10 个问题及问题格式示例。
决策文件
1. 模型为什么选错了工具
v1b 运行中每次错误的工具选择(每项均在 3 次运行中出现 3 次),以及 v2 的处理方式:
| 问题 | 预期 → 实际选择 | 根本原因 | v2 后 |
|---|---|---|---|
| "我的作品集网站仓库的确切名称是什么?" | list_repositories → get_repository | v1 的“获取仓库信息”让模型猜仓库名称(portfolio-website),而不是列出仓库 | 已修复 |
| "哪些仓库已经很久没有推送过了?" | list_repositories → get_recent_commits | “获取某个仓库的近期活动”听起来像是答案;没有说明可以按最后推送时间对仓库排序 | 已修复 |
| "mcp-bench-app 上有哪些主题标签?" | get_repository → search_issues | “搜索 GitHub”让模型以为可以用来查找任何与仓库有关的内容 | 已修复 |
| "有人在 mcp-bench-app 上最新打开的问题是什么?" | list_open_issues → search_issues | 模型选择了搜索,还编造了查询语法(created:desc) | 已修复 |
| "mcp-bench-app 上有哪些问题已关闭?" | search_issues → list_open_issues | 模型向列出未关闭问题的工具传入 state: "closed",相信自己的参数胜过工具名称 | 仍然错误,留出集上也如此 |
2. 消融实验的结果
使用总能选对工具的预言机时,v1 得分为 83%:所有 10 个仅提供仓库名称的问题都失败了,因为 v1 要求使用 owner/name。因此,v1 → v2 的部分提升原本就会来自输入处理。v1b(v1 文本 +
仅提供仓库名称)将两者区分开来:
- v1 → v1b:提升 17 个百分点。 所有 10 个仅提供仓库名称的问题都从 ❌ 变为 ✅。描述文字完全没有变化。
- v1b → v2:提升 15 个百分点,来自 9 个问题:
- 5 个问题只差一句参数说明。 v1 的
limit说明写着“数量”。模型请求 100 条,但架构只允许 50 条,调用因此失败。v2 写明“(1-50)”,这 5 个问题便通过了。 - 4 个问题出在工具描述上:上表中的前四行。
- 5 个问题只差一句参数说明。 v1 的
即使描述含糊,模型也很少选错工具(v1 上选对工具的比例为 92%)。大部分失败都来自被服务端拒绝的调用。
3. 我做了哪些更改
- 每条描述都说明返回内容、适用场景和不适用场景,并指出替代工具,例如
list_open_issues:“请勿用于已关闭的问题、关键词搜索或所有仓库中的问题(使用 search_issues),也不要用于拉取请求(使用 list_pull_requests)。” - 每个边界都沿一个维度互斥划分: 未关闭与已关闭、单个仓库与所有仓库、关键词搜索与不使用关键词、元数据与历史记录、问题与 PR。
- 每个参数都说明了格式和范围,并附有示例。 事实证明,这比工具描述本身更重要。
- 行为修复,单独报告: 裸仓库名称会解析为用户的仓库,列表工具会返回准确总数,而不是单页数量。
- 错误消息会提示下一步操作(“调用 list_repositories 检查确切名称”)。
4. 自动优化器发生了过拟合,防护机制发现了问题
eval/optimize.py 会向模型展示训练失败案例,让模型重写描述,并且只有在训练准确率上升且留出集准确率没有下降时才保留候选结果。从 v1 开始,由 qwen2.5:7b 重写自身工具描述(离线数据,迭代 4 次):
| 迭代 | 训练集(60) | 留出集(20) | 保留 |
|---|---|---|---|
| 0(v1) | 65% | 60% | 基线 |
| 1 | 70% | 55% | 否 |
| 2 | 67% | 50% | 否 |
| 3 | 72% | 40% | 否 |
| 4 | 72% | 40% | 否 |
每次重写都提高了训练集准确率,却降低了留出集准确率,因此防护机制拒绝了所有重写。7B 模型写出的描述适合它刚刚见过的问题,而不适合这些工具。如果没有留出集检查,第 3 次迭代看起来会像是提升了 7 个百分点。最终能泛化的是手写的 v2。
5. 尚待完善之处与权衡
- **剩下的 3 个 v2 未命中项都要求 API 不支持的功能:**拉取请求的
state: "merged"、仓库的sort: "stars",以及未关闭问题工具中的state: "closed"。我没有绕开这些限制改写,因为那样会针对这些特定问题进行调优。正确的修复方式是修改 schema:接受merged(根据merged_at筛选)、在客户端按星标数排序,并让问题工具接受状态参数。 - **较长的描述每个请求会多消耗约 920 个 token。**对于 6 个工具来说,这个代价值得。若有数十个工具,我会改用工具搜索和延迟加载。
- Agent 模式还需要更多问题,才能让数据有参考意义(13 个太少了)。
- 每个列表的分页最多获取 500 个条目(5 页);超过此数量的总数会标记为下限。
使用方法
在 Claude Desktop 中,将以下内容添加到 claude_desktop_config.json(或者像此示例那样,将 command 指向本地安装):
{
"mcpServers": {
"github": {
"command": "uvx",
"args": ["--from", "git+https://github.com/AbdulMuhaiminKhan/github-mcp", "github-mcp"],
"env": { "GITHUB_TOKEN": "github_pat_... (fine-grained, read-only)" }
}
}
}
或者使用 Docker:运行 docker build -t github-mcp .,然后参考
claude_desktop_config.docker.example.json。
GUIDE.md 介绍了令牌权限、种子脚本和完整的评估工作流
(PowerShell 用户请参阅 WINDOWS.md)。
自行运行基准测试
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[dev,eval,plot]'
pytest -q
# No token needed: the offline benchmark repos and a free local model
ollama pull qwen2.5:7b
python eval/run_eval.py --fake-github --toolset v1 --runs 3
python eval/run_eval.py --fake-github --toolset v2 --runs 3
python eval/run_eval.py --fake-github --toolset v2 --questions eval/heldout.jsonl
python eval/run_eval.py --fake-github --mode agent --toolset v2
# Against real GitHub: seed the repos once (see GUIDE.md), then drop --fake-github
python eval/seed_repos.py
# Rebuild the chart and the dashboard from your results
python eval/plot_results.py eval/results/select-*.jsonl -o docs/results.png
python eval/export_dashboard.py
技术栈
Python 3.10+ · MCP Python SDK 2.x · httpx · pydantic · GitHub REST API · pytest · ruff · mypy · GitHub Actions · 用于评估的 Ollama / Anthropic API · matplotlib · 原生 JS 仪表板
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。