# Where Can Web AI Help If Your Code Must Stay Local? Local-first vs. GitHub-first

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

<figure class="local-first-github-first-hero not-prose" lang="en-us">
<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.en-us.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.en-us.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/local-first-vs-github-first-architecture/local-first-github-first.mobile.en-us.svg" type="image/svg+xml"><img src="/images/posts/local-first-vs-github-first-architecture/local-first-github-first.en-us.svg" alt="Local-first returns only directory counts and manually recorded status through a local MCP; GitHub-first lets a cloud workspace read and modify hosted source, with branch, PR, CI and human approval." width="1200" height="700" loading="eager" fetchpriority="high" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption>Architecture boundary sketch: MCP output restrictions do not prove zero egress from the whole Mac. The web tunnel path remains unverified on a real Mac.</figcaption>
</figure>

If a commercial project's source code cannot be sent to a cloud model, what can I still do with ChatGPT on the web? Can it inspect local project status, or request a controlled SSH check? And if the source is allowed to be hosted, why bother with local MCP at all?

The real choice is not a particular tool. It is **what the tool can see, where work executes, and where the result goes**.

In short: the web app does not automatically read your Mac. If you do not provide source code to the model, it can advise from explicitly shared summaries or status, but it cannot independently inspect or debug code it has not seen.

Running Codex locally does not mean inference is local. Codex Cloud is not a connection to your Mac. An MCP tunnel forwards requests but does not itself constrain tool permissions. GitHub Private controls repository access; it does not make a hosted repository local. If source is supplied to a cloud model, that context enters the model's processing boundary. Below is a selection guide followed by two public examples of these boundaries: [VXI Local-First MCP](https://github.com/vxime/vxi-local-first-mcp) and [VXI SSH Ops Bridge](https://github.com/vxime/vxi-ssh-ops-bridge). The first shares local project status; the second allows only a fixed read-only SSH operation.

## Compare the paths: where is the workspace, and what can the model see?

| Path | Workspace and execution location | What the cloud model may see | Suitable use |
|---|---|---|---|
| Codex Local environment in the desktop app + local MCP | Project/worktree, terminal and MCP process on the Mac | Depends on the context actually sent to the model; local execution does not imply local inference | Read/write files, run commands and inspect diffs within the granted scope, when the model's data boundary has been checked |
| Codex Cloud / web cloud task | Selected GitHub repository in an isolated cloud workspace | Source files and other workspace contents available to the task | Modify code, run checks and deliver a branch or result when source is approved for GitHub and cloud processing |
| GitHub cloud coding agent | Repository plus a temporary cloud agent environment | Repository files, task context and material accessible during execution | Collaborate through branches, PRs and checks |
| ChatGPT Web + Secure MCP Tunnel + Local-First MCP | MCP runs locally; the machine initiates an outbound tunnel | Only the counts and status enums explicitly returned by this MCP | Share limited project metadata without granting source-read or shell access |
| ChatGPT Web + Secure MCP Tunnel + SSH Ops Bridge | Local MCP bridge and SSH client reach a configured host | Target alias and a fixed, filtered health response | Request one allowlisted read-only check on a non-production target |
| Web model + a local coding-agent relay (to be built separately) | A local adapter delegates to Codex or another local coding agent | Depends on inference location and what the adapter returns | Possible future design, but requires its own tool, output, write, timeout and approval controls |

One important limitation comes first: the tunnel section below is based on OpenAI's documented setup, but the ChatGPT Web-to-local-MCP end-to-end path has **not** been run successfully on the author's real Mac. Treat the configuration as a guide, not a verified one-click tutorial.

### Choose a path before configuring it

- **Source must never be sent to a cloud model:** keep code-level work in local inference or another trusted local workflow. A metadata-only MCP can share approved status, but a cloud model that cannot see the source cannot independently review it.
- **You only need web ChatGPT to see local project status:** use a fixed-field projection like Local-First MCP; it needs neither source-read access nor a shell.
- **You need a web request for a fixed SSH health check:** use a named operation like SSH Ops Bridge, keeping host identity, private keys and the target allowlist local.
- **Source may be hosted and you want the AI to read and edit it:** use GitHub-first, Codex Cloud or another approved cloud agent, with branches, PRs, commits and CI as reviewable records.
- **You want web AI to delegate code work to local Codex:** build and review a separate local-agent relay. Neither metadata MCP nor the SSH bridge provides that capability.
- **Projects have different data classifications:** choose the route per project. Permission to host a blog repository does not imply permission to send commercial source through the same path.

For other vendors' web tools, check each one independently: verify its supported remote-MCP or local-agent path, authentication, local-agent capability and the exact inputs and tool results sent to the model. MCP support alone does not mean OpenAI Secure MCP Tunnel is reusable with another vendor.

Official references: [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), and [GitHub Copilot cloud agent](https://docs.github.com/en/copilot/concepts/agents/cloud-agent).

## Three data flows

### A. Web AI reads local project status

~~~text
ChatGPT Web
    |
OpenAI Secure MCP Tunnel endpoint
    ^ tunnel-client initiates outbound HTTPS from the local machine
    |
local vxi-local-first-mcp (stdio MCP)
    |
local project directory: count file entries; do not read source contents

Returned: counts + PASS / FAIL / UNKNOWN
Not returned: source, paths, diffs, logs, commands
~~~

### B. Web AI requests a fixed SSH check

~~~text
ChatGPT Web
    |
OpenAI Secure MCP Tunnel endpoint
    ^ tunnel-client initiates outbound HTTPS from the local machine
    |
local vxi-ssh-ops-bridge (stdio MCP)
    |
local SSH client + private key + pinned known_hosts
    |
non-production SSH target: fixed read-only probe script
    |
filtered health / service / storage enums
    |
back to the web app
~~~

### C. GitHub-first cloud development

~~~text
GitHub repository approved for hosting
    |
Codex Cloud / GitHub cloud agent
    |
cloud workspace reads source, edits files and runs checks
    |
branch + PR + commit + CI
    |
human review and approval
~~~

These are different models. The first two constrain what a web tool can see or request; the third allows an AI to run a full source-level development cycle in an approved cloud workspace.

## Do not conflate Codex local, Codex Cloud and MCP

Codex Local environment in the desktop app configures preparation steps and common actions for a local project or worktree. The integrated terminal runs commands on the machine, and Git tooling can inspect diffs, stage, commit, push and create PRs. A local Codex client can also launch a local stdio MCP server; this is a local MCP host starting a local server and does not require a web tunnel.

But “local execution” only tells you where files and commands run. If the model is hosted, source context used for analysis may still be sent to the cloud. Strict source-egress prevention requires local inference or a verified trusted local workflow, plus a separate audit of other processes and network paths on the device.

Codex Cloud instead places the selected GitHub repository in an isolated cloud workspace, prepares dependencies and runs tasks there. GitHub cloud agents can also read source, modify branches, run checks and create PRs in cloud environments. This is appropriate when the project is permitted to cross that cloud boundary, not as a substitute for keeping source on the Mac.

### MCP host versus MCP server

- **Desktop ChatGPT, Codex CLI and IDE extensions as MCP hosts:** they can start local stdio servers using the configuration supported by each client. The documented CLI form is:

~~~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
~~~

For SSH Ops Bridge, use the same command pattern but set VXI_OPS_CONFIG to the local path of ops.config.json and point the server command to vxi-ssh-ops-bridge/bin/vxi-ops.mjs. Keep hostnames, accounts, private-key paths and ops.config.json on the machine; never paste runtime secrets into chat or commit them to Git. The desktop app, Codex CLI and supported IDE extensions can use the Codex host MCP configuration, subject to each client's workspace permissions.

- **ChatGPT Web as the caller:** the web app does not read the local ~/.codex/config.toml and does not automatically inherit the local Codex command menu. It needs a supported remote MCP route. Secure MCP Tunnel lets a local tunnel client connect outward to an OpenAI-hosted endpoint and forward requests to a local stdio or HTTP MCP server.

## How to connect the web app to a local MCP and then request SSH

OpenAI's [Secure MCP Tunnel documentation](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels) describes a client running in the network that can reach the private MCP service. It initiates outbound HTTPS, receives requests from the hosted endpoint, forwards them to the local stdio or HTTP MCP server, and sends responses back through the same tunnel. This does not require exposing a public inbound port on the Mac.

Paths and tunnel IDs in the commands below are placeholders. Keep runtime credentials in local environment variables only.

1. Create or select a tunnel in OpenAI Platform. Confirm that the target ChatGPT workspace and Platform organization are correctly associated and that the necessary Tunnel permission is available.
2. Download the current tunnel-client from the Platform tunnel settings. Check its current help/quickstart output before configuring a separate profile for each MCP server.
3. Test each MCP server offline before configuring the tunnel. For Local-First MCP:

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

After these checks pass, configure the Local-First profile:

~~~bash
export CONTROL_PLANE_API_KEY="<runtime-key kept in the local terminal>"

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
~~~

Absolute paths avoid relying on a background process having the same working directory or PATH as an interactive terminal.

For SSH Ops Bridge, run npm test and npm run demo first. Create a local ops.config.json from ops.config.example.json and change mode from disabled to read_only. Configure only an independently verified non-production target, a low-privilege account, a local private-key path and a pinned known_hosts file. Install the read-only script on the test host at /usr/local/bin/vxi-health-check as described in the repository README. Then configure a separate tunnel 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
~~~

Platform screens and client versions can change, so use the installed client's current help output for exact options.

4. Keep tunnel-client run active. In the supported custom MCP entry point in ChatGPT, select **Add custom MCP server → Tunnel**, choose the available tunnel, authenticate and review the permission prompt. If the tunnel is missing, check workspace/organization association and permissions instead of making the local MCP publicly reachable.
5. Start with the read-only vxi_project_status or vxi_ops_policy tool to confirm discovery, then request vxi_ssh_health if needed. Do not test against a production host; verify the SSH host key through an independent channel.

### What the SSH bridge does locally

The local administrator stores the host, username, port, private-key path and independently verified known_hosts in ops.config.json. The MCP accepts only configured aliases such as lab, not model-generated command strings. The bridge ignores personal SSH configuration, disables agent forwarding and interactive password prompts, calls the fixed /usr/local/bin/vxi-health-check script and reduces the response to three enums.

The web app does not receive your private key and log in directly; it requests a fixed operation. The tunnel transports the request, while the tool list, input validation, local config, host-key binding and remote script jointly define the permission boundary.

If an MCP is replaced by a local agent that can edit code, review its tools, inputs, outputs, file writes, command execution, timeouts and human approval separately. A safety conclusion about a constrained SSH MCP does not transfer automatically to a writable coding-agent relay.

## What the two public repositories do

### VXI Local-First MCP: share local project metadata only

[Open the repository](https://github.com/vxime/vxi-local-first-mcp)

The local stdio MCP exposes two read-only tools:

- **vxi_project_status:** file-category counts, skipped symlink counts, and manually recorded lint/test/build states (PASS / FAIL / UNKNOWN; default UNKNOWN). The MCP does not run tests. The bounded scan skips dot directories and common build/cache folders such as .git, node_modules and dist. It processes at most 20,000 directory entries; exceeding the limit raises an error rather than returning partial counts, so it is not a complete inventory of every possible project file.
- **vxi_security_contract:** describes the fields allowed and prohibited in the response.

The scanner classifies directory entries without reading source-file contents. The fixed projection excludes source, paths, diffs, logs, environment variables and shell access. It lets a cloud model see limited project status; it does not encrypt and upload the source.

The record command is a local CLI operation, not an MCP tool, and it does not run tests. Example:

~~~bash
node bin/vxi-local.mjs record --workspace /path/to/project --kind test --status PASS
~~~

The kind is lint, test or build, and the status is entered by the operator. This is useful only for giving a web model coarse context without uploading source or raw logs. It cannot diagnose a failure and is not a signed test attestation bound to a commit. For debugging, inspect full logs locally or manually select and redact a minimal error summary before sharing it.

### VXI SSH Ops Bridge: fixed read-only SSH operations only

[Open the repository](https://github.com/vxime/vxi-ssh-ops-bridge)

The bridge is disabled by default and exposes only vxi_ops_policy and vxi_ssh_health. The latter accepts an alias from the local allowlist, calls a fixed remote script and filters the result to health, service and storage enums. The example script's health: PASS proves only that the fixed script ran; service and storage remain UNKNOWN until application-specific checks are installed and verified. It does not provide generic exec(command), file transfer, deployment, restart or arbitrary log export.

The repositories are independent. One shares local project status; the other allows one fixed SSH check. Neither has source-editing tools or calls the other.

## What does a green test run actually prove?

These counts refer to different repositories and test layers, not one test suite counted repeatedly:

- **Local-First MCP:** main has 6 unit tests ([baseline CI](https://github.com/vxime/vxi-local-first-mcp/actions/runs/40167991313)). PR #3 adds an Ubuntu/macOS matrix, not new test cases; the same six tests pass 6/6 on each OS ([PR #3 CI](https://github.com/vxime/vxi-local-first-mcp/actions/runs/40170254165)).
- **SSH Ops Bridge:** main has 7 unit tests ([baseline CI](https://github.com/vxime/vxi-ssh-ops-bridge/actions/runs/40167994082)). PR #3 keeps those seven and adds a separate OpenSSH localhost integration job on a one-time GitHub-hosted Ubuntu runner. Its unit and loopback SSH checks pass ([PR #3 CI](https://github.com/vxime/vxi-ssh-ops-bridge/actions/runs/40173329836)).
- **SSH Ops Bridge PR #4:** adds one test case on top of the seven, covering missing or wrongly typed host/user values, and adds the corresponding string validation. Its eight unit tests and demo pass on that still-unmerged Draft branch ([PR #4 CI](https://github.com/vxime/vxi-ssh-ops-bridge/actions/runs/40173291133)).

The loopback fixture exposed real test-design issues: first, the runner user and PAM configuration did not match, causing authentication failure. Later, a server-side ForceCommand was found to override the client request and could create a false positive even when the intended command had not run. The fixture was corrected to omit ForceCommand so the real SSH client executes the generated fixed remote script. Tests check direct and MCP results, reject a wrong pinned host key, and reject arbitrary exec requests.

These results apply only to the exact source versions linked above. At the time of this review, the three source PRs remain Draft and unmerged; the PR #3 cross-platform matrix and loopback job, and the PR #4 input-validation fix, are not part of main.

## Conclusion: choose by data boundary, not by tool name

When source is allowed into the cloud, GitHub-first lets AI read and write code while branches, PRs, commits and CI preserve reviewable engineering evidence. When source must stay local, Local-First MCP can expose status to a web model, and SSH Ops Bridge can offer one fixed read-only remote operation. Source-level development still needs a local human, local inference or another reviewed local workflow.

The boundary to protect is not simply “local tool” or “MCP connected.” It is what each path actually sends to the model, which operations the model can request, and where the response goes.

Both repositories are minimal prototypes. The live tunnel path has not been verified on the author's Mac; this work also did not test an external SSH host, perform a whole-device source-egress audit, verify genuinely local model inference, or compare quality, latency, token use and cost on the same task.
