PAYATHON 2026

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_dispatchContents: write事件本身不会更新文件
调用 workflow_dispatchActions: 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

服务端依次执行以下操作:

  1. 使用 GitHub App 私钥签发 App JWT。
  2. 调用 POST /app/installations/{installation_id}/access_tokens。
  3. 使用返回的 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 的用户授权:

  1. 客户端向你的 MCP 授权服务器发起授权请求。
  2. 授权服务引导用户完成 GitHub 登录、授权及必要的 App 安装。
  3. GitHub 回调到你的服务,服务端交换并保存 GitHub token。
  4. 你的授权服务器向 MCP 客户端签发独立的 MCP token。
  5. 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 提交代码,不要依赖这次提交再次触发 push workflow;可以在同一次运行中部署,或显式触发部署 workflow。GitHub 的递归触发限制
  • 远程 MCP 服务可以让仓库操作脱离本地电脑。依赖本地 VM 的任务仍需另行迁移到云端,手机上的会话也必须能够发现并调用该 connector。Claude 的远程 connector 文档要求正确的 OAuth discovery 和 S256 PKCE,应采用目标客户端实际支持的授权流程。Claude connector 授权要求

备注:内容仅供参考。