MCP 客户端如何在不使用长期有效 PAT 的情况下向 GitHub 提交代码?
结论
可以不使用长期有效的 PAT。对于只有一个私有仓库、固定向 main 提交网站文件的场景,建议采用:
远程 MCP server + 标准 MCP OAuth 授权 + GitHub App installation access token。
客户端通过 OAuth 获取访问 MCP server 的令牌;MCP server 在服务端生成短期 GitHub installation token,再调用 GitHub API 提交文件。GitHub 凭据无需保存在客户端配置或聊天记录中。
你提出的方案 1 和方案 2 都可行,需要自行部署服务,并实现授权和工具逻辑。MCP 规范允许授权服务器与 MCP server 分开部署,也讨论了连接第三方 API 的 MCP proxy 架构。这个架构已有规范支持,但注册自己的 GitHub App 后,官方远程 GitHub MCP 地址并不会自动兼容客户端。MCP 授权规范、MCP proxy 安全说明
方案 3 中,不同操作需要的权限和能完成的工作有所不同:
| 操作 | GitHub App 所需仓库权限 | 能否更新网站源码 |
|---|---|---|
| 创建或更新普通文件 | Contents: write | 可以 |
调用 repository_dispatch | Contents: write | 事件本身不会更新文件 |
调用 workflow_dispatch | Actions: write | 取决于 workflow 的实现 |
因此,使用 repository_dispatch 仍需授予 Contents: write 权限。workflow_dispatch 可以让调用方无需拥有仓库内容写权限,但仍需解决新内容的存放和获取问题。Repository dispatch API、Workflow dispatch API
先分清两层授权
这套架构使用两种用途不同的令牌:
Claude 桌面端或移动端
│
│ MCP access token
▼
远程 MCP server
│
│ GitHub App installation access token
▼
GitHub API
│
▼
提交到 main → GitHub Actions → Azure Static Web Apps
第一层控制谁可以调用 MCP 服务,以及可以调用哪些工具。第二层控制服务可以对哪个 GitHub repository 执行哪些操作。
客户端只需完成第一层的通用 remote-MCP OAuth 流程。第二层可以完全由服务器处理,客户端无需理解 GitHub App 的 user-to-server 授权。
MCP server 应验证客户端提交的 MCP bearer token 确实面向自己的服务,再使用独立的 GitHub 凭据访问下游 API,不能直接把该令牌转发给 GitHub。MCP 安全规范明确禁止未经正确验证的 token passthrough。令牌透传限制
GitHub 的 Claude 安装指南仍列出了官方远程 server 在 Desktop 自定义 connector 中的 OAuth 兼容限制。本方案连接的是自己部署的 MCP 地址。GitHub 官方安装指南
单仓库场景的最小可行架构
1. 创建专用 GitHub App,只安装到目标仓库
安装时选择 Only select repositories,仅勾选网站仓库。
创建和更新普通网站文件需要以下权限:
Contents: Read and write。- GitHub App 默认具备的
Metadata: Read-only。
仅提交网站文件无需授予 Actions: write。修改 .github/workflows/ 下的 workflow 文件还需要 Workflows: write,建议让 MCP 服务拒绝修改这个目录。文件内容 API 权限要求
GitHub 的仓库选择和权限配置可以限制仓库范围及操作类别。只允许向 main 提交、只允许修改指定网站文件等限制,需要由 MCP server 执行。
2. 把服务部署到独立于电脑的运行环境
服务需要具备以下条件:
- 客户端可访问的 HTTPS MCP endpoint。
- 能访问 GitHub API 的出站网络。
- 用于客户端登录和同意授权的 OAuth 服务。
- 存放 GitHub App 私钥的服务端 secret storage。
- 对调用者身份、仓库、分支和文件路径的检查。
自用服务也必须检查调用者身份。如果任何人登录后都能使用你的 installation credential,其他用户就会获得私有仓库的写入能力。
授权部分可以使用现成组件。例如,Cloudflare 官方提供了基于 Workers OAuth Provider 的远程 MCP 实现方式,可处理发现、客户端注册和 token endpoint。登录、同意授权的页面以及 GitHub 工具逻辑仍需接入。这个模式已有实际实现,但不能据此认定已有完全满足你需求的一键代理。Cloudflare MCP 授权实现
3. 在服务端按需生成 installation token
服务端依次执行以下操作:
- 使用 GitHub App 私钥签发 App JWT。
- 调用
POST /app/installations/{installation_id}/access_tokens。 - 使用返回的 installation token 调用仓库 API。
生成 token 时,还可以通过 repository_ids 和 permissions 进一步缩小权限范围,例如:
{
"repository_ids": [123456789],
"permissions": {
"contents": "write"
}
}
这里的仓库 ID 是示例值。installation token 的有效期为一小时,没有 refresh token,过期后由服务端重新生成。GitHub 的 Octokit SDK 可以处理 token 的生成和到期后的重新获取。Installation token 文档
采用这种方式,客户端无需保存长期 PAT,服务器仍需妥善保管 GitHub App 私钥这份长期凭据。
4. 暴露有限的读写工具
助手至少需要能够读取当前文件,再提交修改。例如:
read_site_file(path)
write_site_file(path, content, expected_sha)
服务端应固定 repository 和 main,并校验路径白名单。不要让模型自由传入 installation ID、任意仓库或任意 GitHub API URL。
这套流程无需 working tree:助手通过工具读取 GitHub 上的文件,将修改后的内容交给写入工具,服务器再通过 API 创建 commit。
电脑关机前尚未提交的本地修改不会自动出现在 GitHub。若要继续处理这些修改,必须提前将它们同步到云端可访问的位置。
提交单个文件的代码示例
GitHub Contents API 可以直接创建 commit,无需执行 git clone 或 git push:
PUT /repos/{owner}/{repo}/contents/{path}
文件内容使用 Base64 编码。更新已有文件时,必须传入被替换文件的 blob SHA,且应使用助手编辑前读取到的 SHA。Contents API 参数
以下服务端代码使用 @octokit/app,只允许读写 index.html。示例假定 App 仅安装到目标仓库,并且只授予了普通文件读写权限。
import { App } from "@octokit/app";
const app = new App({
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY,
});
const octokit = await app.getInstallationOctokit(
Number(process.env.GITHUB_INSTALLATION_ID)
);
const target = {
owner: process.env.GITHUB_OWNER,
repo: process.env.GITHUB_REPO,
path: "index.html",
};
export async function readIndex() {
const { data } = await octokit.request(
"GET /repos/{owner}/{repo}/contents/{path}",
{ ...target, ref: "main" }
);
if (data.type !== "file" || data.encoding !== "base64") {
throw new Error("Unsupported file response");
}
return {
content: Buffer.from(data.content, "base64").toString("utf8"),
sha: data.sha,
};
}
export async function writeIndex(content, expectedSha) {
if (
typeof content !== "string" ||
typeof expectedSha !== "string" ||
expectedSha.length === 0
) {
throw new Error("content and expectedSha are required");
}
const { data } = await octokit.request(
"PUT /repos/{owner}/{repo}/contents/{path}",
{
...target,
branch: "main",
message: "Update website",
content: Buffer.from(content, "utf8").toString("base64"),
sha: expectedSha,
}
);
return {
commit_sha: data.commit.sha,
commit_url: data.commit.html_url,
};
}
getInstallationOctokit() 是 Octokit 提供的 installation 身份接口。Octokit App 文档
这段代码不包含 MCP transport、OAuth endpoint 和工具注册。MCP handler 必须先验证调用者身份与写入权限,再调用这些函数。私钥由部署平台注入,不能作为工具参数传入。
创建新文件时,使用相同的 PUT endpoint,省略 sha 即可。发生并发冲突后,应重新读取并合并内容,不能自动换成最新 SHA 后覆盖。一次发布涉及多个文件时,逐文件调用会产生多个 commit;如果需要原子提交,应使用 Git Database API 组织一次完整提交。
使用独立 GitHub App token 更新 main,可以沿用已有的 push 部署流程,前提是满足 workflow 的分支、路径过滤条件及仓库规则。GitHub 文档也将 App installation token 列为触发后续 workflow 的认证方式。Workflow 触发规则
如果需要 user-to-server 授权,刷新能否留在服务端?
可以由服务端处理刷新。当服务必须代表某个 GitHub 用户操作时,MCP 服务可以作为 OAuth client,完成 GitHub App 的用户授权:
- 客户端向你的 MCP 授权服务器发起授权请求。
- 授权服务引导用户完成 GitHub 登录、授权及必要的 App 安装。
- GitHub 回调到你的服务,服务端交换并保存 GitHub token。
- 你的授权服务器向 MCP 客户端签发独立的 MCP token。
- MCP 工具调用时,服务端使用保存的 GitHub user access token。
GitHub App user access token 的有效权限是用户与 App 权限的交集,仓库访问范围也受双方可访问资源的限制。若要严格限定为单个仓库,应使用专用 App、限制安装范围,并在服务端固定目标仓库。GitHub App 用户令牌权限
启用 expiring user access tokens 后,access token 通常在八小时后到期,refresh token 在六个月后到期。服务端通过以下 endpoint 刷新:
POST https://github.com/login/oauth/access_token
Accept: application/json
Content-Type: application/x-www-form-urlencoded
client_id=YOUR_GITHUB_APP_CLIENT_ID&
client_secret=YOUR_GITHUB_APP_CLIENT_SECRET&
grant_type=refresh_token&
refresh_token=STORED_REFRESH_TOKEN
上述字段全部由服务端读取或构造,不应出现在聊天记录中。
刷新会返回新的 access token 和 refresh token,并使旧令牌失效。服务端需要原子保存新令牌,并避免同一用户的并发刷新。refresh token 过期或授权被撤销后,用户仍需重新授权。GitHub 用户令牌刷新文档
客户端到 MCP 服务的令牌刷新由客户端与 MCP 授权服务器处理,和 GitHub token 的刷新相互独立。对于单人维护一个网站的场景,installation token 通常更省事,无需引入 GitHub user-to-server token。
只触发部署时,如何使用 workflow_dispatch?
如果只需要重新部署 GitHub 中已有的代码,可以给 GitHub App 授予 Actions: write,由服务器触发指定 workflow:
POST /repos/OWNER/REPO/actions/workflows/WORKFLOW_FILE.yml/dispatches
Authorization: Bearer INSTALLATION_ACCESS_TOKEN
Accept: application/vnd.github+json
Content-Type: application/json
{
"ref": "main"
}
workflow 必须已配置 workflow_dispatch,并满足该事件的触发要求。Workflow dispatch API
Azure 自动生成的 workflow 还可能在部署 job 上限制事件类型:
if: github.event_name == 'push' || ...
因此,除了增加以下配置:
on:
push:
branches: [main]
workflow_dispatch:
还要检查 job 和 step 的 if: 条件,确保 workflow_dispatch 能进入构建部署流程,同时保留原有的 PR 处理逻辑。Azure Static Web Apps 构建配置
如果助手生成了新文件,单纯调用 dispatch 仍只会部署仓库现有内容。要让调用方无需拥有内容写权限,可以先把新内容存入受控云存储,再让 workflow 根据对象 ID 获取并部署。这会增加一条内容传输链路。若最终仍需把文件提交回仓库,执行提交的 workflow 或其他服务仍需拥有内容写权限。
Actions: write 的权限范围不限于触发某一个 workflow。若只希望开放网站部署,应由 MCP server 固定 workflow、ref 和允许的输入。
上线前需要确认的边界
Contents: write不会自动绕过 branch protection 或 ruleset。若main要求 PR,应增加创建 PR 的流程和相应权限。- 返回 commit URL 只能说明提交成功。部署结果应通过独立的状态查询或 GitHub Actions 页面确认。
- 如果 workflow 使用自己的
GITHUB_TOKEN提交代码,不要依赖这次提交再次触发pushworkflow;可以在同一次运行中部署,或显式触发部署 workflow。GitHub 的递归触发限制 - 远程 MCP 服务可以让仓库操作脱离本地电脑。依赖本地 VM 的任务仍需另行迁移到云端,手机上的会话也必须能够发现并调用该 connector。Claude 的远程 connector 文档要求正确的 OAuth discovery 和
S256PKCE,应采用目标客户端实际支持的授权流程。Claude connector 授权要求
备注:内容仅供参考。
