# 代码不上云，网页端 AI 能参与到哪一步？Local-first 与 GitHub-first 的接入边界

> Canonical: https://vxi.me/posts/local-first-vs-github-first-architecture/
> Locale: zh-cn
> Published: 2026-10-25
> Updated: 2026-10-25
> Markdown: https://vxi.me/posts/local-first-vs-github-first-architecture.md

<figure class="local-first-github-first-hero not-prose" lang="zh-cn">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/local-first-vs-github-first-architecture/local-first-github-first.mobile.still.zh-cn.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/local-first-vs-github-first-architecture/local-first-github-first.still.zh-cn.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/local-first-vs-github-first-architecture/local-first-github-first.mobile.zh-cn.svg" type="image/svg+xml"><img src="/images/posts/local-first-vs-github-first-architecture/local-first-github-first.zh-cn.svg" alt="Local-first 只通过本地 MCP 返回计数与人工录入的状态；GitHub-first 在云端工作区读取源码、修改和测试，经过分支、PR、CI 与人工批准。" width="1200" height="700" loading="eager" fetchpriority="high" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption>架构边界示意：MCP 输出约束不等于整机零外发；网页端隧道互通尚未在真实 Mac 验证。</figcaption>
</figure>

如果商业项目的源码不能发给云端模型，我还可以用网页端 ChatGPT 做什么？能不能让它查看本地项目状态，或者从网页上请求一次受控的 SSH 检查？如果源码允许托管，又为什么还要折腾本地 MCP？

真正要选的不是某个工具，而是让工具看见什么、在哪里执行、结果又返回到哪里。

一句话版：网页端不会自动读取你的 Mac；如果不把源码交给模型，它只能依据你明确开放的摘要或状态给建议，不能直接审查或调试没看到的代码。

本地运行 Codex，不等于模型在本地推理；Codex Cloud 不是连接到你的 Mac；MCP 隧道负责转发请求，不会自动限制工具权限；GitHub Private 只限制仓库访问权限，仓库本身仍托管在云端，若把代码交给云端模型，相关上下文也会进入模型的处理范围。下面先给出选择方法，再用两个公开示例说明接入路径和实际边界。

我为这类边界写了两个独立的公开示例：[VXI Local-First MCP](https://github.com/vxime/vxi-local-first-mcp) 和 [VXI SSH Ops Bridge](https://github.com/vxime/vxi-ssh-ops-bridge)。前者只分享本地项目状态，后者只允许请求固定的只读 SSH 操作。

## 先比较接入路径：工作区在哪里，模型能看到什么？

| 路径 | 工作区与命令执行位置 | 云端模型可能看到什么 | 能力与适用场景 |
|---|---|---|---|
| ChatGPT 桌面应用中的 Codex Local environment + 本地 MCP | Mac 上的项目 / worktree；本地终端与 MCP 进程 | 取决于模型实际收到的上下文；本地执行不等于本地推理 | 在授权范围内读写本地文件、运行命令、查看 diff；适合已核实模型数据边界的项目 |
| Codex Cloud / 网页端 Cloud task | 连接的 GitHub 仓库进入隔离的云端工作区 | 云端任务可读取工作区源码和文件 | 修改代码、运行测试、交付分支或结果；适合源码获准进入 GitHub 和云端环境的项目 |
| GitHub 云端 Coding Agent | GitHub 仓库与 Agent 的临时云端环境 | 仓库文件、任务上下文和运行中可访问的内容 | 修改分支、运行检查、创建 PR；适合以 GitHub 作为协作与验收中心的团队 |
| ChatGPT 网页端 + Secure MCP Tunnel + Local-First MCP | MCP 服务运行在本机；本机主动建立出站隧道 | 该 MCP 明确返回的计数和状态枚举 | 查看有限项目元数据；适合只需项目状态、不需要源码级审查的场景 |
| ChatGPT 网页端 + Secure MCP Tunnel + SSH Ops Bridge | 本机 / 网络内主机运行 MCP 桥接器与 SSH 客户端 | 目标别名和固定格式的检查结果 | 请求一项预先限定的只读远端检查；适合只需查看非生产目标有限状态的场景 |
| 网页端模型 + 本地 coding-agent relay（需另建） | 本机适配器委派给本地 Codex / coding agent | 取决于模型推理位置和适配器回传内容 | 可设计为委派本地代码任务，但必须限制工具和输出；适合愿意另建 relay 的场景 |

先说明一个影响预期的事实：隧道部分是依据 OpenAI 官方流程整理，尚未在真实 Mac 上跑通 ChatGPT Web 与这两个 server 的端到端互通。后文是配置参考，不是已验收的一键教程。

### 先选路，再看怎么接

- **源码一字节都不能交给云端模型：** 代码级工作留给本地推理或本地可信流程。Local-First MCP 可以分享获准的元数据，但云端看不到源码，就不能独立完成源码级审查。
- **只想让网页端查看本地项目状态：** 使用 Local-First MCP 这类固定字段投影；它不需要获得源码读取或 Shell 权限。
- **只想从网页端请求 SSH 只读检查：** 使用 SSH Ops Bridge 这类命名操作，把主机身份、私钥和目标白名单留在本地。
- **源码允许托管，而且希望 AI 真正读写代码：** 选择 GitHub-first、Codex Cloud 或其他获准的云端 Agent，利用分支、PR、commit 和 CI 留下可复核记录。
- **希望网页端把代码任务委派给本地 Codex：** 需要另建并审查 coding-agent relay；元数据 MCP 和 SSH 桥接器都不承担这项工作。
- **多个项目的数据等级不同：** 按项目分别选择路径，不要因为博客仓库可以上云，就把同一套权限扩展到商业源码。

其他厂商的网页端工具需要单独核验：先确认它是否支持远程 MCP 或本地 Agent、使用什么认证方式，再看模型实际收到哪些输入和工具返回；不能因为都支持 MCP，就假设 OpenAI Secure MCP Tunnel 可以直接复用。

官方说明可参照：[Codex Local environments](https://developers.openai.com/codex/environments/local-environment)、[Codex Cloud environments](https://developers.openai.com/codex/environments/cloud-environments)、[Codex MCP](https://developers.openai.com/codex/extend/mcp)、[OpenAI Secure MCP Tunnel](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) 和 [GitHub Copilot cloud agent](https://docs.github.com/en/copilot/concepts/agents/cloud-agent)。

## 先看三条数据流

### A. 网页端读取本地项目状态

```text
ChatGPT Web
    |
OpenAI Secure MCP Tunnel endpoint
    ^ 本机 tunnel-client 主动建立出站 HTTPS
    |
本机 vxi-local-first-mcp（stdio MCP）
    |
本机项目目录：只统计文件类别，不读取文件内容

返回：计数 + PASS / FAIL / UNKNOWN
不返回：源码、路径、diff、日志、命令
```

### B. 网页端请求固定 SSH 检查

```text
ChatGPT Web
    |
OpenAI Secure MCP Tunnel endpoint
    ^ 本机 tunnel-client 主动建立出站 HTTPS
    |
本机 vxi-ssh-ops-bridge（stdio MCP）
    |
本机 SSH 客户端 + 本地私钥 + pinned known_hosts
    |
非生产 SSH 目标：固定的只读探测脚本
    |
过滤后的 health / service / storage 枚举
    |
返回网页端
```

### C. GitHub-first 云端开发

```text
获准托管的 GitHub 仓库
    |
Codex Cloud / GitHub 云端 Agent
    |
云端工作区读取源码、修改文件、运行检查
    |
分支 + PR + commit + CI
    |
人工审查与批准
```

这三条路径解决的不是同一件事：前两条限制云端工具能看到或能请求什么；第三条则允许 AI 在获准的云端工作区中对源码做完整的开发循环。

## Codex 本地工作区、Codex Cloud 与 MCP，不要混成一个概念

ChatGPT 桌面应用中的 Codex Local environment 用于配置本地项目 / worktree 的准备步骤和常用动作，集成终端在本机执行命令，Git 工具可以查看 diff、暂存、提交、推送和创建 PR。Codex 的本地客户端也可以直接启动本机 stdio MCP 服务；这种连接是“本地 MCP host 启动本地 MCP server”，并不需要为了本地客户端调用而建立网页端隧道。

但“本地执行”只说明文件和命令在哪里运行。若模型本身是托管模型，分析时使用的源码上下文仍可能被发送到云端。只有本地推理或经过验证的本地可信流程，才可能满足严格的源码不出站要求；还需要单独审查设备上的其他进程和网络路径。

Codex Cloud 则会把选定的 GitHub 仓库放进隔离的云端工作区，准备依赖并运行任务。GitHub 云端 Agent 也能在云端环境中读取源码、修改分支、运行检查并创建 PR。这种路线更适合允许进入云端边界的项目，而不是“源码只留在 Mac”的替代方案。

### MCP host 与 MCP server 的区别

- **ChatGPT 桌面应用、Codex CLI 与 IDE 扩展作为 MCP host：** 可以按客户端支持的方式配置本地 stdio MCP，工具进程由本地客户端启动。官方 CLI 命令形式是 `codex mcp add <name> --env KEY=VALUE -- <server-command>`；例如接入本地项目状态服务：

```bash
codex mcp add vxi-local-status \
  --env "VXI_WORKSPACE=/absolute/path/to/project" \
  -- /absolute/path/to/node /absolute/path/to/vxi-local-first-mcp/bin/vxi-local.mjs mcp

codex mcp list
```

若要接入 SSH Ops Bridge，可以用同一命令形式，但把环境变量改为 `VXI_OPS_CONFIG=/absolute/path/to/ops.config.json`，并把 server command 指向 `vxi-ssh-ops-bridge/bin/vxi-ops.mjs mcp`。配置中的主机名、账号、私钥路径和 `ops.config.json` 只留在本机；不要把运行时密钥贴进聊天或提交到 Git。ChatGPT 桌面应用、Codex CLI 与 IDE 扩展共享 Codex host 的 MCP 配置；在桌面应用的 Settings → MCP servers 中添加服务器，具体选项受工作区权限影响。

- **ChatGPT 网页端作为调用方：** 网页不会读取本机的 `~/.codex/config.toml`，也不会自动继承本地 Codex 的命令菜单。它需要使用支持的远程 MCP 连接方式；Secure MCP Tunnel 的作用，就是让本机主动连接 OpenAI 的隧道端点，再把请求转发给本地 MCP。

## 网页端怎样连接本机 MCP，再触发 SSH？

OpenAI 的 [Secure MCP Tunnel 文档](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) 描述了这条连接方式：在能访问私有 MCP 服务的网络内运行 `tunnel-client`，由它主动建立出站 HTTPS 通道，从 OpenAI 托管的端点取回请求、转发给本地 stdio 或 HTTP MCP server，再把响应通过同一隧道送回去。它不要求给本机 MCP 开公共入站端口。

命令中的路径和隧道 ID 是占位符，运行时密钥只放在本机环境变量中。

1. 在 OpenAI Platform 创建或选择隧道，确认目标 ChatGPT workspace 和 Platform organization 已正确关联，并取得所需的 Tunnel 权限。
2. 从 Platform 的隧道设置获取最新的 `tunnel-client`，先运行 `tunnel-client help quickstart`，再为每个 MCP server 配置独立 profile。
3. 先离线运行并测试 MCP server，再配置隧道。进入 `vxi-local-first-mcp` 仓库，先跑合成测试和演示：

```bash
cd /path/to/vxi-local-first-mcp
node --version  # >= 22
npm test
npm run demo
```

确认通过后，再为 Local-First MCP 配置隧道：

```bash
export CONTROL_PLANE_API_KEY="<runtime-key，仅保存在本地终端>"

tunnel-client init \
  --sample sample_mcp_stdio_local \
  --profile vxi-local-status \
  --tunnel-id "<your-tunnel-id>" \
  --mcp-command "/usr/bin/env VXI_WORKSPACE=/absolute/path/to/project /absolute/path/to/node /absolute/path/to/vxi-local-first-mcp/bin/vxi-local.mjs mcp"

tunnel-client doctor --profile vxi-local-status --explain
tunnel-client run --profile vxi-local-status
```

这里使用绝对路径，是为了避免后台进程的工作目录或 `PATH` 与交互式终端不同。

如果要接入 SSH Ops Bridge，先在仓库中运行 `npm test` 和 `npm run demo`，再从 `ops.config.example.json` 创建本地的 `ops.config.json`，并将 `mode` 从 `disabled` 改为 `read_only`。配置中只放经过独立核验的非生产目标、专用低权限账户、私钥路径和固定的 `known_hosts` 文件；同时按仓库 README 将只读脚本安装到测试主机的 `/usr/local/bin/vxi-health-check`。然后为这个 MCP server 单独配置 profile：

```bash
tunnel-client init \
  --sample sample_mcp_stdio_local \
  --profile vxi-ssh-readonly \
  --tunnel-id "<your-ssh-tunnel-id>" \
  --mcp-command "/usr/bin/env VXI_OPS_CONFIG=/absolute/path/to/ops.config.json /absolute/path/to/node /absolute/path/to/vxi-ssh-ops-bridge/bin/vxi-ops.mjs mcp"

tunnel-client doctor --profile vxi-ssh-readonly --explain
tunnel-client run --profile vxi-ssh-readonly
```

平台界面与客户端版本可能变化，实际参数以当前客户端的帮助输出为准。

4. 保持 `tunnel-client run` 运行。在 ChatGPT 当前支持的自定义 MCP 入口中选择 **Add custom MCP server → Tunnel**，选择可用隧道，完成认证并确认权限提示。若找不到隧道，优先检查 workspace / organization 关联与权限，而不是把 MCP 服务改成公网开放。
5. 先调用只读的 `vxi_project_status` 或 `vxi_ops_policy` 验证工具发现，再按需请求 `vxi_ssh_health`。不要用真实生产主机测试；SSH 主机指纹必须通过独立渠道核验。

### SSH 桥接器在本机做了什么？

本地管理员把主机名、用户名、端口、私钥路径和经过独立核验的 `known_hosts` 写入本地 `ops.config.json`。MCP 只接受诸如 `lab` 的允许别名，不接受模型生成的命令文本。桥接器忽略个人 SSH 配置，禁用 Agent 转发和交互式密码提示，调用固定的 `/usr/local/bin/vxi-health-check`，并把输出收敛为三个状态枚举。

所以网页端不是直接拿着你的私钥登录服务器；它请求的是一个固定操作。隧道负责传输，桥接器的工具清单、参数校验、本地配置、主机密钥绑定和远端脚本共同决定权限边界。

如果把 MCP 后面换成可编辑代码的本地 Agent，必须重新审查它每个工具的输入、输出、文件写入、命令执行、超时和人工批准机制。一个受限 SSH MCP 的安全结论，不能自动套用到有代码写入权限的 relay 上。

## 两个公开仓库各自承担什么角色？

### VXI Local-First MCP：只分享本地项目元数据

[打开仓库](https://github.com/vxime/vxi-local-first-mcp)

这个本机 stdio MCP 暴露两个只读工具：

- `vxi_project_status`：文件分类计数、跳过的符号链接数量，以及人工记录的 `lint / test / build` 状态（`PASS / FAIL / UNKNOWN`；默认是 `UNKNOWN`，MCP 不会自动运行测试）。计数是有界扫描结果：会跳过点目录和常见构建 / 缓存目录（如 `.git`、`node_modules`、`dist`），最多处理 20,000 个目录项；超过上限会报错，而不是返回部分计数，因此不是全项目全量统计。
- `vxi_security_contract`：说明允许输出与禁止输出的字段。

文件分类通过目录项完成，不读取文件内容；固定字段投影不包含源码、路径、diff、日志、环境变量或 Shell 接口。它的作用是让云端看到有限的项目状态，而不是把源码“加密后传上去”。

`record` 是本机 CLI 操作，不是 MCP 工具，也不会自动运行测试。调用方式是 `node bin/vxi-local.mjs record --workspace /path/to/project --kind test --status PASS`；`kind` 可选 `lint`、`test` 或 `build`，状态由操作者手动填写。它的实际用途很有限：把本地检查的粗粒度结果带入网页端讨论，而不上传源码或原始日志；它能帮助模型了解当前状态，却不能定位失败原因，也不是绑定 commit 的签名测试证明。需要排错时，应在本机分析完整日志，或由人工挑选并脱敏最小错误摘要再分享。

### VXI SSH Ops Bridge：只允许固定的只读 SSH 操作

[打开仓库](https://github.com/vxime/vxi-ssh-ops-bridge)

桥接器默认关闭，只暴露 `vxi_ops_policy` 与 `vxi_ssh_health`。后者接收本地配置允许的目标别名，调用固定远端脚本，并将返回值过滤成 `health / service / storage` 三个枚举。当前示例脚本的 `health: PASS` 只证明脚本成功运行；在接入并验证应用专属检查前，`service` 和 `storage` 仍为 `UNKNOWN`。它不提供通用 `exec(command)`、文件传输、部署、重启或任意日志导出。

两个仓库彼此独立：一个只暴露本地项目状态，另一个只暴露固定 SSH 检查；它们没有代码读写工具，也不会互相调用。

## 测试绿灯究竟证明了什么？

这些数字对应不同仓库、不同测试层级，不是同一组测试反复计数：

- **Local-First MCP：** `main` 基线有 6 个单元测试（[基线 CI](https://github.com/vxime/vxi-local-first-mcp/actions/runs/40167991313)）。PR #3 没有增加测试用例，只增加 Ubuntu / macOS 矩阵；同样的 6 项在两个系统上分别 6/6 通过（[PR #3 CI](https://github.com/vxime/vxi-local-first-mcp/actions/runs/40170254165)）。
- **SSH Ops Bridge：** `main` 基线有 7 个单元测试（[基线 CI](https://github.com/vxime/vxi-ssh-ops-bridge/actions/runs/40167994082)）。PR #3 保留这 7 项单元测试，并另加一次性 GitHub-hosted Ubuntu Runner 上的 OpenSSH localhost 集成作业；该运行的单元测试和回环 SSH 检查均通过（[PR #3 CI](https://github.com/vxime/vxi-ssh-ops-bridge/actions/runs/40173329836)）。
- **SSH Ops Bridge PR #4：** 在 7 项基础上新增 1 个测试用例，专门覆盖主机名 / 用户名缺失或类型不正确的情况；同时补上相应的字符串类型校验。该 PR 分支的 8/8 单元测试和 Demo 通过（[PR #4 CI](https://github.com/vxime/vxi-ssh-ops-bridge/actions/runs/40173291133)）。

回环测试也暴露过真实的夹具问题：早期 Runner 用户与 PAM 配置不匹配，导致认证失败；后来又发现服务端 `ForceCommand` 会覆盖客户端请求，可能让测试在没执行预期命令时产生假阳性。修正后，夹具明确不配置 `ForceCommand`，让真实 `ssh` 客户端执行代码生成的固定远端脚本；测试同时检查直接调用和经 MCP 调用的结果，并确认错误的 pinned host key 与任意 `exec` 请求会被拒绝。

这些结果只对应各自链接中的源码版本。截至本次核对，三个源代码 PR 仍为 Draft、尚未合并；PR #3 的跨平台矩阵与回环作业、PR #4 的输入校验修复都还不属于 `main`。

## 结论：按数据边界选择，而不是按工具名字选择

如果源码允许进入云端，GitHub-first 能让 AI 直接读写代码，并用分支、PR、commit 与 CI 留下持久的工程证据。如果源码必须留在本机，Local-First MCP 可以让网页端参与状态检查，SSH Ops Bridge 可以让网页端请求一项固定的只读远端操作；代码级开发则仍需本地人工、本地推理或其他经过审查的本地流程。

真正要守住的边界，不是“用了本地工具”或“接上了 MCP”，而是每条路径实际把什么交给了模型、模型能调用什么、返回内容又去了哪里。

这两份仓库目前仍是最小原型。除文中说明的隧道互通尚未真机验证外，本轮也没有测试外部 SSH 主机、整机源码出站审计、真正的本地模型推理，或同一任务的质量 / 耗时 / Token / 费用对比。
