mac-developer-bridge
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Basarisiz
- exec() — Shell command execution in lib/federation.mjs
- spawnSync — Synchronous process spawning in lib/federation.mjs
- fs.rmSync — Destructive file system operation in lib/federation.mjs
- process.env — Environment variable access in lib/federation.mjs
- exec() — Shell command execution in mcp-http.mjs
- fs.rmSync — Destructive file system operation in mcp-http.mjs
- process.env — Environment variable access in mcp-http.mjs
- rm -rf — Recursive force deletion command in menubar/build.sh
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Give ChatGPT a real terminal on your Mac. Open-source MCP bridge for shell, files, PTY sessions, jobs, and Codex history.
Mac Developer Bridge
Give ChatGPT a real terminal on your Mac.
Mac Developer Bridge turns a ChatGPT conversation into the reasoning layer for your actual Mac. It can run shell commands, edit files, start interactive terminal sessions, manage long-running jobs, and read stored Codex threads without starting another Codex model turn.
Example: “Find the Codex session I was working on yesterday, inspect the live repo, fix CI, push the result, and tell me what changed.”
That is the kind of workflow this project is built for.
[!WARNING]
Mac Developer Bridge deliberately gives an MCP client the effective permissions of your macOS user. It is not sandboxed and has no command or path allowlist. Read SECURITY.md before enabling it.
The idea
ChatGPT has the reasoning. Your Mac has the source code, terminal, credentials, build tools, local services, and work in progress. Mac Developer Bridge connects the two over MCP without adding another model or agent loop in the middle.
flowchart LR
A[ChatGPT] -->|MCP| B[Mac Developer Bridge]
B --> C[Shell, Git and local CLIs]
B --> D[Filesystem]
B --> E[Real PTY sessions]
B --> F[Background jobs]
B --> G[Stored Codex history]
B --> H[Audit log and kill switch]
The bridge itself makes no OpenAI model call. It exposes deterministic local tools; ChatGPT supplies the reasoning. The Codex-history tools use read-only codex app-server methods and never call turn/start.
What this unlocks
- Recover a stored Codex thread, inspect the repo it refers to, and continue the work from ChatGPT.
- Run tests, builds, Git, package managers, database CLIs, AppleScript, and other tools already installed on your Mac.
- Keep interactive shells and terminal programs alive through a real PTY instead of pretending stdin is a terminal.
- Start long-running local jobs, inspect their logs later, and stop the whole process group.
- Read and modify files anywhere your macOS user can access.
This is intentionally different from a local coding agent. There is no second reasoning loop. ChatGPT remains the agent; the Mac is the execution environment.
Quick start
For a personal ChatGPT account, the menu-bar app is the easiest path. You need macOS, Node.js 18+, cloudflared, a hostname/tunnel, and ChatGPT Developer mode.
git clone https://github.com/alexanderradahl/mac-developer-bridge.git
cd mac-developer-bridge
./menubar/build.sh
open /Applications/MacDevBridge.app
Use Start, then Copy ChatGPT Setup from the menu-bar app. The detailed OAuth and Cloudflare setup is in Connecting to ChatGPT and DEPLOY.md.
Workspace users who have access to OpenAI Secure MCP Tunnel can use install.sh instead. See Transports.
Want to see what to ask it to do? Start with the copy-paste workflows.
If this is useful, star the repo so other developers can find it. If you build something interesting with it, share the workflow in Discussions.
This is an independent open-source project and is not an official OpenAI or Cloudflare product. OpenAI, ChatGPT, Codex, and Cloudflare are trademarks of their respective owners.
Open source
Mac Developer Bridge is released under the MIT License. Bug reports and focused pull requests are welcome; see CONTRIBUTING.md. Security-sensitive reports should follow the guidance in SECURITY.md rather than being posted publicly.
Capabilities
- Arbitrary shell commands through
/bin/zsh -lc, under the logged-in macOS user - Detached background jobs with persistent stdout/stderr logs, status inspection, and process-group termination
- Unrestricted file read, write, append, list, stat, copy, move, chmod, symlink, mkdir, and recursive delete
- Unified-diff application through
git apply - Stored Codex thread discovery and reading without resuming a thread or starting a Codex model turn
- Paginated Codex turn retrieval for histories too large for a single response
- Local JSONL auditing
- Outbound-only private connectivity through OpenAI Secure MCP Tunnel, or a plain-HTTP loopback front end that Cloudflare Tunnel publishes over HTTPS
- Per-user persistence through a macOS LaunchAgent
- Fail-closed unlock latch:
bridge.mjsre-reads the unlock file before every tool call, so removing it refuses the next call and exits — unless the process inheritedMAC_DEV_BRIDGE_FULL_ACCESS_ACK, which bypasses the file entirely - Local kill switch (
scripts/disable.sh), which stops the front end, the bridge, detachedshell_startjob groups, interactive pty sessions, and federated child MCP servers, verifying the same targets it signalled
Git, package managers, Vercel CLI, database CLIs, AppleScript, browser CLIs, build tools, and other installed programs remain reachable through shell_exec; the bridge deliberately maintains no command allowlist.
Tools
| Tool | Purpose |
|---|---|
bridge_status |
Runtime identity, paths, permissions context, shell, audit mode, and Codex binary |
shell_exec |
Run any foreground shell command, optionally with cwd, env, stdin, timeout, and output cap |
shell_start |
Start a detached long-running process |
shell_job_status |
Inspect running state and log tails |
shell_job_list |
List persistent job metadata |
shell_job_kill |
Signal a background process group |
fs_read |
Read text or base64 with offset pagination |
fs_write |
Atomic replace, create, append, or binary write |
fs_list |
Recursive or non-recursive directory listing |
fs_stat |
lstat metadata and symlink target |
fs_manage |
mkdir, remove, move, copy, chmod, or symlink |
apply_patch |
Apply or check a unified diff with git apply |
codex_thread_read |
Read a stored Codex thread without resuming it |
codex_thread_list |
Search and page stored Codex threads |
codex_thread_turns_list |
Page stored turns with full, summary, or omitted items |
audit_tail |
Read the local bridge audit tail |
Interactive terminal sessions
A real pty, allocated by lib/ptyhelper.pl (core Perl, no dependency added). Advertised only when the helper runs on this host; otherwise the six tools are absent rather than broken.
| Tool | Purpose |
|---|---|
pty_start |
Start a program on a real terminal and return a session id |
pty_read |
Read the transcript from a byte cursor, optionally long-polling |
pty_write |
Send keystrokes, including control characters |
pty_resize |
Change the window size, confirmed by a kernel read-back |
pty_signal |
Signal the session's process group |
pty_close |
End the session and reclaim it |
Limits that will be visible in normal use:
- Line length. While the terminal is in canonical mode — the default, and what every interactive prompt uses — the line discipline discards an input line of 1024 bytes or more instead of truncating it.
pty_writerefuses such a write withPTY_WRITE_CANON_LIMITrather than reporting bytes the program will never see. Bytes accumulate across calls until a\ror\n, so chunking does not evade it. Send lines of at most 1023 bytes. A session that has put its terminal in raw mode is checked and allowed. - Concurrency. The session cap is taken, not merely checked, so concurrent
pty_startcalls cannot exceed it. - Retention. Each session keeps the last
MAC_DEV_BRIDGE_PTY_RING_BYTESof output in a fixed ring;pty_readreportslostByteswhen a cursor falls behind it. - Containment. See SECURITY.md —
pty_closereportsleaderGroupGone,ttyProcessesKilledanduncontainedPidsseparately, andcontainmentVerifiedis true only when nothing survived.
Federated child MCP servers
If a provider registry is configured, each provider's tools are advertised with a key__tool prefix and proxied. There is no built-in provider: the registry is operator-supplied. Personal-browser-profile mode requires a per-use operator grant — see SECURITY.md.
Bridge environment
These are read by bridge.mjs on both transports.
| Variable | Default | Purpose |
|---|---|---|
MAC_DEV_BRIDGE_DATA_DIR |
~/Library/Application Support/MacDeveloperBridge |
State, job metadata, federation roots. |
MAC_DEV_BRIDGE_LOG_DIR |
~/Library/Logs/MacDeveloperBridge |
Log directory. |
MAC_DEV_BRIDGE_AUDIT_LOG |
$LOG_DIR/audit.jsonl |
Audit JSONL path. |
MAC_DEV_BRIDGE_AUDIT_MODE |
metadata |
off, metadata, or full. full records tool arguments; see the caveat in SECURITY.md. |
MAC_DEV_BRIDGE_UNLOCK_FILE |
$DATA_DIR/FULL_ACCESS_ENABLED |
The revocable unlock latch. Re-read before every tool call. |
MAC_DEV_BRIDGE_UNLOCK_RECHECK_MS |
3000 |
How often the latch is re-read while a pty session or a federated child exists and the client is silent. Bounds how long either can outlive a removed unlock file. |
MAC_DEV_BRIDGE_SHELL |
login shell | Shell used for shell_exec/shell_start. |
MAC_DEV_BRIDGE_DEFAULT_OUTPUT_BYTES |
1000000 |
Default per-call output cap. |
MAC_DEV_BRIDGE_MAX_OUTPUT_BYTES |
8000000 |
Ceiling a call may request. |
MAC_DEV_BRIDGE_PTY_PERL |
/usr/bin/perl |
Interpreter for the pty helper. |
MAC_DEV_BRIDGE_PTY_HELPER |
lib/ptyhelper.pl beside bridge.mjs |
Helper script path. |
MAC_DEV_BRIDGE_PTY_MAX_SESSIONS |
8 (1–64) |
Live session cap. kern.tty.ptmx_max is 511 system-wide, so this protects the operator's own Terminal.app, not just this process. |
MAC_DEV_BRIDGE_PTY_RING_BYTES |
262144 (4 KiB–4 MB) |
Per-session output retention. Total retention is this times the session cap. |
MAC_DEV_BRIDGE_PTY_IDLE_TIMEOUT_MS |
900000 (1 s–1 h) |
Idle reclaim window, and a ceiling: pty_start may request a shorter one, never a longer. A live session's effective value is in bridge_status. |
MAC_DEV_BRIDGE_PTY_MAX_LIFETIME_MS |
28800000 (5 s–24 h) |
Hard ceiling, enforced even on an actively used session. |
MAC_DEV_BRIDGE_PTY_START_TIMEOUT_MS |
5000 |
How long pty_start waits for the helper to report a real pty. |
MAC_DEV_BRIDGE_MCP_SERVERS |
— | Path to a child-MCP provider registry JSON file. |
MAC_DEV_BRIDGE_MCP_SERVERS_JSON |
— | The same registry inline. Takes precedence. |
MAC_DEV_BRIDGE_MCP_START_DEADLINE_MS |
15000 (1 s–120 s) |
Wall-clock ceiling on one provider's whole startup — handshake, grant check, and every tools/list page. A provider that exceeds it is abandoned rather than left holding up the tool surface. |
MAC_DEV_BRIDGE_MCP_PING_IDLE_MS |
30000 |
Idle interval after which a federated child is pinged; a child that fails the ping is treated as hung and restarted. |
MAC_DEV_BRIDGE_PERSONAL_APPROVAL_FILE |
$DATA_DIR/PERSONAL_BROWSER_APPROVED |
Where the single-use personal-browser grant is read from. The bridge never creates it. |
MAC_DEV_BRIDGE_FULL_ACCESS_ACK |
— | Environment form of the acknowledgement. Not revocable; see below. |
What “full access” means
The MCP server runs with the effective permissions of the macOS account that launches it. It has no path allowlist, shell-command allowlist, sandbox, or internal per-command approval gate.
macOS still enforces TCC privacy controls, Full Disk Access, ACLs, SIP, Keychain access controls, and sudo authentication. Non-interactive MCP shell calls do not magically provide a sudo password or a terminal UI. Configure passwordless sudo only when you deliberately want that separate escalation.
The bridge refuses to start until a deliberate acknowledgement exists, and re-checks it before every tool call — so removing the acknowledgement file both prevents future starts and stops a running bridge at its next call.
The environment form (MAC_DEV_BRIDGE_FULL_ACCESS_ACK) is deliberately not revocable that way: a bridge that inherited it never reads the file, so deleting the file does not stop it. The Install steps below export that variable, so a bridge started from such a shell is only stoppable by stopping the process. The menu bar app strips it from its children for exactly this reason.
ChatGPT action permissions and confirmation behavior are separate. The MCP server advertises write and destructive annotations honestly and cannot bypass restrictions enforced by the ChatGPT product or workspace.
Transports
The bridge speaks MCP over stdio. Two transports can carry it to ChatGPT.
OpenAI Secure MCP Tunnel (install.sh, documented below) is outbound-only
and needs no public endpoint. It requires the Tunnel connection type in
ChatGPT's plugin dialog, which is not available on personal accounts — the
option renders but is disabled.
Cloudflare Tunnel + Server URL (mcp-http.mjs) is the fallback when Tunnel
is unavailable. mcp-http.mjs fronts the bridge with Streamable HTTP on127.0.0.1:8787 behind OAuth 2.1 (and a static bearer for other clients),
and cloudflared publishes it:
export MAC_DEV_BRIDGE_HTTP_TOKEN="$(openssl rand -hex 32)"
node mcp-http.mjs
ChatGPT's plugin dialog offers Authentication: OAuth, No Auth, or
Mixed — there is no API-key/bearer field.mcp-http.mjstherefore implements
an OAuth 2.1 authorization server as well, and that is how you connect ChatGPT.
See Connecting to ChatGPT below. The static bearer token still works for any
client that can send anAuthorization: Bearerheader.
The host is pinned to loopback and the path to /mcp, deliberately — the only
intended peer is cloudflared on the same machine.
Environment:
| Variable | Default | Purpose |
|---|---|---|
MAC_DEV_BRIDGE_HTTP_TOKEN |
— | Bearer token. Minimum 24 bytes, printable ASCII. Refuses to start without one. |
MAC_DEV_BRIDGE_HTTP_TOKEN_FILE |
— | Read the token from a mode-0600 file instead, keeping it out of ps eww. Takes precedence. |
MAC_DEV_BRIDGE_HTTP_PORT |
8787 |
Loopback port. |
MAC_DEV_BRIDGE_HTTP_TIMEOUT_MS |
600000 |
Per-request ceiling, for long shell_exec calls. |
MAC_DEV_BRIDGE_ENTRY |
bridge.mjs beside mcp-http.mjs |
Test-only seam for substituting a stub bridge. Changing it means scripts/disable.sh will not recognise the child. |
MAC_DEV_BRIDGE_PUBLIC_URL |
derived from Host |
Pins the OAuth issuer. Pin it: Host is client-controllable, and the issuer must match what the client discovered. |
MAC_DEV_BRIDGE_OAUTH_CLIENT_ID |
generated | The client id pasted into ChatGPT. Stable across restarts. |
MAC_DEV_BRIDGE_OAUTH_REDIRECT_URIS |
— | Extra exact-match callbacks, comma-separated. Appends to the built-ins. |
MAC_DEV_BRIDGE_OAUTH_CLIENT_SECRET |
— | Optional second factor on /token, enforced via client_secret_post or client_secret_basic. Put the same value in ChatGPT's OAuth Client Secret field. Scrubbed from child environments. |
MAC_DEV_BRIDGE_BODY_IDLE_TIMEOUT_MS |
30000 |
Drops a request whose body stalls this long. Idle, not total, so a slow-but-progressing upload is not truncated. |
MAC_DEV_BRIDGE_MAX_BUFFERED_BYTES |
100663296 (96 MiB) |
Global budget for buffered request bodies. Exceeding it sheds load with a retryable 503. |
Understand the difference in exposure before choosing this one. The Tunnel
transport makes only outbound connections. This one publishes an HTTPS endpoint
that fronts unrestricted shell access, with a single bearer token as the entire
barrier. Rotate the token if it is ever disclosed, and consider Cloudflare
Access in front of it for a second factor.
Not yet automated for this transport: install.sh requires tunnel-client and
rejects a missing tunnel_... id, so it cannot install the HTTP path, and there
is no LaunchAgent — nothing restarts mcp-http.mjs or cloudflared after a
reboot or a crash. scripts/doctor.sh does cover this transport.uninstall.sh removes the files but does not stop a running front end.
Connecting to ChatGPT
ChatGPT's plugin dialog offers three Authentication choices — OAuth, No
Auth, Mixed — and no API-key/bearer field, so the static bearer token has
nowhere to be entered. mcp-http.mjs therefore implements an OAuth 2.1
authorization server, and that is how ChatGPT connects.
Fill in the dialog as follows:
| Field | Value |
|---|---|
| Connection | Server URL |
| Server URL | https://<hostname>/mcp |
| Authentication | OAuth |
| Registration method | User-Defined OAuth Client |
| OAuth Client ID | logged at startup, or set MAC_DEV_BRIDGE_OAUTH_CLIENT_ID |
| OAuth Client Secret | leave blank |
| Token endpoint auth method | none |
| Default scopes | mcp |
| OIDC enabled | untick |
The menu bar app's Copy ChatGPT Setup produces this list pre-filled.
Untick OIDC because /.well-known/openid-configuration is served only as an alias
of the OAuth metadata and deliberately omits every signing and subject field. No ID
token is issued, so an OIDC-strict client should abort rather than demand one.
ChatGPT then opens a consent page served by your own machine. It names the exact
callback it will redirect to and asks for the bridge token, which is how it knows
the approval came from you. Read the "Will redirect to" line before approving —
any /connector/oauth/<token> path is a valid ChatGPT connector, including one
someone else created.
Use a named Cloudflare tunnel. A quick tunnel's hostname changes on every start,
and that hostname is the OAuth issuer — so a restart between discovery and callback
makes the issuer stop matching what ChatGPT recorded, and a strict client drops the
callback silently. A named tunnel also means creating the connector once instead of
every run.
Endpoints served: /.well-known/oauth-protected-resource,/.well-known/oauth-authorization-server, /.well-known/openid-configuration
plus /.well-known/oauth-protected-resource/mcp,/.well-known/oauth-authorization-server/mcp, /.well-known/openid-configuration/mcp
and /mcp/.well-known/openid-configuration — seven paths in total, since the/mcp/-prefixed form exists only for openid-configuration. Then /authorize,/token, /revoke, /revoke-all, and /healthz. A 401
from /mcp carries WWW-Authenticate: Bearer resource_metadata="…", which is what
lets a client discover the rest.
Menu bar app (HTTP transport)
menubar/ builds a small AppKit status-bar app that owns the two processes this
transport needs and surfaces the three things you actually use: the public URL,
the bearer token, and whether the endpoint is answering.
./menubar/build.sh # also installs a copy to /Applications
open /Applications/MacDevBridge.app
The build installs to /Applications (falling back to ~/Applications) because
Launchpad and Spotlight do not surface apps living in ~/Downloads. The bundle
locates mcp-http.mjs via MAC_DEV_BRIDGE_HOME, then a package next to itself,
then a path baked into Info.plist at build time — so the installed copy still
finds the package.
The menu gives you: current status, the tunnel mode, Copy Server URL, Copy
OAuth Client ID, Copy ChatGPT Setup (the whole dialog filled in, in order),
Copy Bearer Token, Start/Stop, Rotate Token, Open Logs, and Quit.
It prefers a named Cloudflare tunnel when ~/.cloudflared/config.yml declares
one, giving a stable URL — otherwise a quick tunnel, whose hostname changes every
start and forces the ChatGPT connector to be recreated each time. The menu shows
which mode is active.
Why it is worth using over the raw commands:
- It is the supervisor. Start spawns
mcp-http.mjsandcloudflared; Stop and
Quit stop exactly what it started, rather than discovering processes by name. - Start writes the unlock file and Stop removes it, so stopping is fail-closed
throughbridge.mjs's per-call latch, not merely a process kill. - The token lives in a mode-0600 file and is passed by
MAC_DEV_BRIDGE_HTTP_TOKEN_FILE,
keeping it out ofps eww. - Status is polled from
/healthzand from the child processes' liveness, so a
child dying is reported rather than assumed away. - On launch it reclaims orphans — both children.
applicationWillTerminatedoes
not run on a force-quit, crash, or hard reboot, so a previous run could leave the
unlock file armed, the front end serving, andcloudflaredstill publishing a
public hostname. Launching disarms the latch and stops whatever is recorded inmcp-http.pidandcloudflared.pid, each identity-checked first because pids get
recycled and those files surviveSIGKILLand reboot. Reclaiming only the front end
previously left a public ingress that no later run could close, and that the next
Start would re-arm alongside a second tunnel. - It never passes
MAC_DEV_BRIDGE_FULL_ACCESS_ACKto its children. That variable is a
standing unlock inbridge.mjs, so inheriting it would make Stop unable to revoke
anything — and the install docs tell you to export it. - One child dying stops the other. Reporting a failure while leaving the sibling alive
leftcloudflaredpublishing with the latch still armed and the menu reading
"not running". - Children inherit the login shell
PATH, soshell_execbehaves the same as
it does in a terminal (a GUI-launched app otherwise has no nvm or Homebrew).
The app is ad-hoc signed and not notarized. It locates mcp-http.mjs viaMAC_DEV_BRIDGE_HOME, then a package next to the bundle, then a path baked intoInfo.plist at build time — so the /Applications copy works with the package
left where it is. Rebuild after moving the package so the baked path stays correct.MAC_DEV_BRIDGE_HOME.
It does not replace scripts/disable.sh: detached shell_start jobs outlive the
front end by design, and only that script reclaims them from the job registry.
Prerequisites
Both transports:
- macOS and a logged-in desktop user.
- Node.js 18 or newer.
- ChatGPT Developer mode.
- A working
codexCLI only for the three Codex-history tools. Shell and filesystem access do not depend on Codex.
OpenAI Secure MCP Tunnel additionally requires:
- The official
tunnel-clientbinary, downloaded from OpenAI Platform Tunnels or the official OpenAI GitHub release, executable and available onPATHor at~/.local/bin/tunnel-client. - An OpenAI tunnel ID scoped to the ChatGPT workspace that will use it.
- A runtime API key whose principal has Tunnels Read + Use.
- The Tunnel connection option in the ChatGPT plugin dialog.
Cloudflare Tunnel + Server URL additionally requires:
cloudflared, authenticated to a Cloudflare account.- A hostname you control, or a quick-tunnel URL.
opensslfor generating the bearer token.- The Server URL connection option with OAuth — see Connecting to ChatGPT. There is no No-Auth mode:
/mcpis hardcoded with no override, andmcp-http.mjsrefuses to start without a token.
Developer-mode availability is controlled by the account rollout and workspace policy. If the Developer mode toggle is absent, this package cannot override that product-side limitation. The Tunnel option specifically is unavailable on personal accounts — it renders but is disabled — which is why the HTTP transport exists.
Install
Clone the repository (or download a release/archive) and open Terminal in its folder:
git clone https://github.com/alexanderradahl/mac-developer-bridge.git
cd mac-developer-bridge
Then install:
chmod +x install.sh uninstall.sh bridge.mjs scripts/*.sh
export MAC_DEV_BRIDGE_FULL_ACCESS_ACK='I_UNDERSTAND_THIS_GRANTS_FULL_ACCESS'
export CONTROL_PLANE_TUNNEL_ID='tunnel_0123456789abcdef0123456789abcdef'
# Hidden input; the key is not placed in shell history.
read -r -s -p 'Tunnel runtime API key: ' CONTROL_PLANE_API_KEY; printf '\n'
export CONTROL_PLANE_API_KEY
./install.sh
unset CONTROL_PLANE_API_KEY MAC_DEV_BRIDGE_FULL_ACCESS_ACK
The installer can also prompt for the tunnel ID and runtime key when run interactively. The runtime key is stored in the macOS login Keychain and is not written into the package, tunnel profile, or LaunchAgent plist.
The installer:
- Requires the exact full-access acknowledgement.
- Validates macOS, Node, tunnel-client, Codex discovery, tunnel ID format, audit mode, and shell.
- Copies the bridge to
~/.local/share/mac-developer-bridge. - Creates
~/Library/Application Support/MacDeveloperBridge/FULL_ACCESS_ENABLEDwith mode 0600. - Runs syntax, MCP protocol, filesystem, patch, process, secret-scrubbing, and Codex-adapter tests.
- Stores the runtime key in Keychain.
- Creates a unique
tunnel-clientstdio profile and runstunnel-client doctor. - Installs and starts a persistent per-user LaunchAgent.
Custom locations are supported with the MAC_DEV_BRIDGE_INSTALL_DIR, MAC_DEV_BRIDGE_BIN_DIR, MAC_DEV_BRIDGE_PLIST_DIR, MAC_DEV_BRIDGE_DATA_DIR, and MAC_DEV_BRIDGE_LOG_DIR environment variables.
Connect ChatGPT
- Enable Developer mode in ChatGPT.
- Open ChatGPT Plugins and create a developer-mode app.
- Set the connection, per transport:
- Tunnel transport: choose Tunnel, then select or paste the same tunnel ID used during installation. Unavailable on personal accounts — the option renders but is disabled.
- HTTP transport: choose Server URL and enter
https://<hostname>/mcp. Authentication offers only OAuth, No Auth, or Mixed — see Connecting to ChatGPT; the bearer token has no field in this dialog.
- Review and enable the tools, and tick the risk acknowledgement.
- Start a new Chat conversation, select the app, and call
bridge_status.
Suggested first prompt:
Use only the Mac Developer Bridge app for local-machine operations.
First call bridge_status and report the effective user, home directory, shell, Codex binary, audit mode, and whether the tunnel runtime key was scrubbed from child command environments.
Then call codex_thread_read with:
{"thread_id":"019fa926-dbbd-7d72-aa0c-8edd41bd585c","include_turns":true}
If the result is too large, call codex_thread_turns_list in ascending order with items_view="full" and continue through nextCursor until the complete persisted history is recovered.
Do not invoke codex, codex exec, codex-reply, turn/start, or any OpenAI API from shell commands. The Chat conversation is the reasoning agent. Inspect the repository and branch referenced by the thread, report the current state, and continue the unfinished work.
Ask before production deployments, destructive database operations, credential changes, force pushes, or deleting user data.
codex_thread_read and codex_thread_turns_list use local codex app-server read APIs. The bridge does not expose any Codex method that starts a model turn.
Full Disk Access
Check the current state before guessing:
scripts/tcc-doctor.sh # add --open to jump to the settings pane
It probes a TCC-protected path as node and as $MAC_DEV_BRIDGE_SHELL — those two only — and reports which hold
the grant. Full Disk Access cannot be granted from a script — the TCC databases
are SIP-protected, so they are unwritable even as root, and tccutil can only
reset entries. A human must add the binary in System Settings, or an MDM must
push a PPPC profile.
If reads fail with EPERM or “Operation not permitted,” grant Full Disk Access to the actual executables in the runtime chain:
- the exact
nodebinary shown bybridge_status /bin/zsh- the installed
tunnel-clientbinary, for the Tunnel transport only
cloudflared does not need it: it only forwards HTTP to loopback and never
touches the filesystem on a tool's behalf.
A LaunchAgent may not inherit privacy permissions previously granted to Terminal or to a different Node installation. Full Disk Access is separate from ordinary POSIX permissions.
Operations
The ~/.local/share/mac-developer-bridge paths below exist only if install.sh
ran, which requires tunnel-client — so on the HTTP transport that directory
does not exist and you run the scripts from the extracted package directory
instead.
Both transports:
# Full diagnostic report (includes the Full Disk Access check)
./scripts/doctor.sh # or ~/.local/share/mac-developer-bridge/scripts/doctor.sh
# Kill switch. Read its output; a non-zero exit means NOT contained.
./scripts/disable.sh
# Audit log
tail -f "$HOME/Library/Logs/MacDeveloperBridge/audit.jsonl"
HTTP transport:
# Logs (only populated if you redirected them, as DEPLOY.md step 2 does)
tail -f "$HOME/Library/Logs/MacDeveloperBridge/http.stderr.log"
# Restart: there is no LaunchAgent, so stop and re-run it.
# disable.sh REMOVES the unlock file, so it must be recreated — without this the
# front end starts and /healthz answers 200 while every tool call fails 503,
# because /healthz never spawns the bridge.
./scripts/disable.sh
printf 'I_UNDERSTAND_THIS_GRANTS_FULL_ACCESS\n' \
> "$HOME/Library/Application Support/MacDeveloperBridge/FULL_ACCESS_ENABLED"
chmod 600 "$HOME/Library/Application Support/MacDeveloperBridge/FULL_ACCESS_ENABLED"
export MAC_DEV_BRIDGE_HTTP_TOKEN='<the same token the plugin uses>'
node mcp-http.mjs >>"$HOME/Library/Logs/MacDeveloperBridge/http.stderr.log" 2>&1 &
Tunnel transport:
launchctl print "gui/$(id -u)/com.openai.mac-developer-bridge-tunnel"
launchctl kickstart -k "gui/$(id -u)/com.openai.mac-developer-bridge-tunnel"
# Re-enable after an explicit acknowledgement (requires the LaunchAgent plist)
export MAC_DEV_BRIDGE_FULL_ACCESS_ACK='I_UNDERSTAND_THIS_GRANTS_FULL_ACCESS'
~/.local/share/mac-developer-bridge/scripts/enable.sh
unset MAC_DEV_BRIDGE_FULL_ACCESS_ACK
# Rotate the tunnel runtime key with hidden input
~/.local/share/mac-developer-bridge/scripts/rotate-tunnel-key.sh
tail -f "$HOME/Library/Logs/MacDeveloperBridge/tunnel.stderr.log"
enable.sh currently requires the LaunchAgent plist, so it does not work on the
HTTP transport. To re-enable there, recreate the unlock file and restart the
front end as in DEPLOY.md Option B.
tunnel-client normally exposes loopback health endpoints and an operator UI at http://127.0.0.1:8080/healthz, /readyz, /metrics, and /ui while running.
Auditing
The default mode is metadata. It records:
- timestamp and tool name
- a redacted preview of arguments
- SHA-256 hash of the complete arguments
- a compact result summary or error
On the HTTP transport there is no installation, and a GUI-launched menu bar app has
no shell environment to inherit — so the only ways to change audit mode there are to
export it in a shell and start mcp-http.mjs from that shell, or to launch the app
with open -a MacDevBridge --env MAC_DEV_BRIDGE_AUDIT_MODE=full. Otherwise it stays
at metadata.
Set MAC_DEV_BRIDGE_AUDIT_MODE before installation to one of:
MAC_DEV_BRIDGE_AUDIT_MODE=off
MAC_DEV_BRIDGE_AUDIT_MODE=metadata
MAC_DEV_BRIDGE_AUDIT_MODE=full
full can persist sensitive command arguments and file content even after common token-pattern redaction. Treat the audit log as sensitive. The tunnel runtime key is removed from the bridge process environment before any shell or filesystem tool can run, although unrestricted shell access can still reach other credentials available to the macOS account.
Verify usage routing
The bridge itself contains no OpenAI inference client and the Codex adapters call read-only app-server methods. Even so, verify the account-specific behavior after connection:
- Record the current Codex/Work credit balance.
- In Chat, call only
bridge_statusandfs_staton a harmless path. - Refresh the Codex/Work usage page.
- Confirm no Codex model usage was recorded.
- Then read the stored Codex thread and continue the work here.
Do not use shell_exec to run Codex itself if the purpose is to avoid Codex model usage.
Uninstall
From the extracted package or installed directory:
./uninstall.sh
The uninstaller removes the LaunchAgent, bridge installation, command symlink, unlock file, and Keychain runtime key.
It does not remove the data directory, so these survive an uninstall — including two live credentials:
http-token— the bearer token (mode 0600)oauth-state.json— the OAuth client id plus access/refresh token digests (mode 0600)oauth-client-id,mcp-http.pid,cloudflared.pid,jobs/, and the audit log
Delete ~/Library/Application Support/MacDeveloperBridge as well if you want the credentials gone. It also does not stop a running front end; run scripts/disable.sh first.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi