GitHub MCP Server 开发规范指南:新贡献者建立全局认知的 5 个切入点
【免费下载链接】github-mcp-serverGitHub's official MCP Server项目地址: https://gitcode.com/GitHub_Trending/gi/github-mcp-server
这是 GitHub 官方用 Go 写的 MCP Server,把 GitHub API 包装成 AI 客户端能直接调用的工具。代码库里工具很多,但几乎都遵循同一套约定。把这份开发规范当成贡献指南读一遍,你就能明白每个目录的分工、一个工具的完整生命周期,以及那些藏在细节里的规矩——写第一个 PR 前花十分钟读它,能少走不少弯路。
五分钟看懂项目骨架:哪些目录归谁管
cmd/github-mcp-server/ 可执行入口(启动、文档生成、scope 列表等子命令) internal/ 项目内部代码:MCP 协议层、OAuth、日志缓冲、性能分析 pkg/github/ 所有工具的实现(actions / issues / gists / search……) pkg/errors/ 错误分级与响应包装 pkg/inventory/ 工具注册中心(toolset 元数据、ServerTool) pkg/scopes/ OAuth scope 检查 docs/ 安装指南与功能说明 script/ 开发脚本(test、generate-docs、list-scopes……) ui/ 内嵌交互应用(React + Vite)怎么理解这个划分?cmd只负责把命令接到核心逻辑上;真正被外部引用的是pkg;internal是 Go 的编译器级保护,外部模块根本 import 不进来——协议、OAuth、观测这些"管道"代码全在这,比如 internal/ghmcp/ 负责 MCP 协议层。而pkg/github/是仓库的主体:每个文件对应一个 GitHub 领域(actions、issues、gists……),里面是一组工具定义。你 90% 的改动都会发生在这一层。
解剖一个完整工具:以 list_gists 为例
仓库里的工具长得很像,拿最完整的 pkg/github/gists.go 过一遍,以后看别的工具就是走马观花。
注册部分——NewTool把四样东西打包成一个ServerTool:所属 toolset、工具定义、scope 声明、handler:
func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { return NewTool( ToolsetMetadataGists, mcp.Tool{ Name: "list_gists", Description: t("TOOL_LIST_GISTS_DESCRIPTION", "List gists for a user"), Annotations: &mcp.ToolAnnotations{ Title: t("TOOL_LIST_GISTS", "List Gists"), ReadOnlyHint: true, }, InputSchema: WithPagination(&jsonschema.Schema{ /* username, since */ }), }), nil, // 本工具不额外要求 scope func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // 提取参数 → 拿 client → 调 API → 返回 JSON }, ) }handler 内部的固定节奏,从提取到返回一共五步:
username, err := OptionalParamstring if err != nil { return utils.NewToolResultError(err.Error()), nil, nil // 参数错:给 AI 看得懂的提示 } pagination, err := OptionalPaginationParams(args) client, err := deps.GetClient(ctx) // client 来自 context,不自己 new // 调 API → 非 200 走错误包装 → 成功则 marshal 成 JSON 返回值得注意的"why":deps是调用时从 context 里取出来的,注册时并不闭包任何实例。因为远端模式下服务器可能按请求创建实例,闭包会白白多一层分配。而 scope 声明交给NewTool统一推导——你只写最小所需权限,AcceptScopes会按层级自动展开(要求public_repo时,持有更宽的repo令牌也放行)。
藏在细节里的约定:按重要程度排个序
参数提取统一走泛型 helper,而不是各写各的。全部工具都用 pkg/github/params.go 里的RequiredParam/OptionalParam,泛型负责类型断言,错误文案全局一致。数字参数更是层层设防:不少 MCP 客户端会把数字当字符串传过来,所以toInt还要拒绝 NaN、无穷、小数和超 int 范围的值——这就是为什么仓库里没有args["page"].(float64)这种直白断言。
HTTP 响应体必须随手关闭。每次 API 调用之后紧跟一行:
defer func() { _ = resp.Body.Close() }()这行小代码是评审时会被盯着看的点,不是风格偏好,是泄漏防御。
错误分三层,别混用。
| 层 | 返回方式 | 场景 |
|---|---|---|
| 参数错 | NewToolResultError | AI 能读到的提示,引导它改参数重试 |
| API 错 | NewGitHubAPIErrorResponse | 把状态码和响应体包进去,方便排查 |
| 系统错 | fmt.Errorf+%w | 序列化失败等内部异常 |
区分前两层的意义在于:参数错让 AI 自己纠正,API 错则保留现场。
分页只许用现成 helper。WithPagination管 REST 的 page/perPage,WithUnifiedPagination和WithCursorPagination管游标,默认值(page=1、perPage=30)集中在OptionalPaginationParams一处。工具层不发明自己的分页参数,否则 AI 会困惑同一功能在不同工具里参数名不一样。
上线前最后四道关
- scope 要声明:工具需要哪些权限在
NewTool里写明,最小化是硬性习惯;机制细节见 docs/scope-filtering.md。 - 测试带 race:仓库的测试入口就一条
go test -race ./...(见 script/test),本地跑绿再提 PR。GraphQL 侧已有 mock(internal/githubv4mock),不必真打 API。 - 文档要重新生成:工具描述由
cmd/github-mcp-server/generate_docs.go自动生成(对应 script/generate-docs)。改了工具定义没重新生成,文档就会悄悄过期。 - 文案走翻译函数:所有
Description都经过t(key, default),这样部署方能用环境变量覆盖文案,一行代码不用改。
第一天就该知道的 5 件事
- 先跑一遍
script/test,保证本地基线是绿的。 - 精读一个完整工具(
ListGists就很好),比读十篇文档管用。 - 写工具照抄
ListGists的骨架:NewTool+ 参数 helper + 错误三层,别自创模式。 - 所有面向 AI 的文案过翻译函数,错误消息写得像"给 AI 的提示",不是日志。
- 动手前翻一眼 docs/ 和 CONTRIBUTING.md,功能开关、工具重命名这些机制都有现成说明。
骨架同构、helper 统一、细节有坑——抓住这三点,你在这个仓库里就不会跑偏。
【免费下载链接】github-mcp-serverGitHub's official MCP Server项目地址: https://gitcode.com/GitHub_Trending/gi/github-mcp-server
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考