ChatGPT iPhone Bridge
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ChatGPT iPhone BridgeOpen apple.com in Safari on my iPhone"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ChatGPT iPhone Bridge
Control Mobile Safari on one USB-connected iPhone from ChatGPT through OpenAI Secure MCP Tunnel and Appium MCP. An opt-in cellular mode can instead control a dedicated Bridge Browser app on an iPhone over cellular or Wi-Fi.
This is an unofficial project. It opens no public inbound port and does not expose Appium directly to the internet.
Architecture
ChatGPT
-> OpenAI Secure MCP Tunnel
-> tunnel-client on your Mac
-> Local iPhone MCP server
-> XCUITest / WebDriverAgent
-> USB-connected iPhone
-> Mobile SafariThe cellular path is separate:
ChatGPT
-> OpenAI Secure MCP Tunnel
-> this bridge on your online Mac
-> end-to-end encrypted Cloudflare relay
-> Bridge Browser app over cellular
-> WKWebViewThe cellular app is not Safari and does not expose Appium compatibility tools.
The bridge preserves the upstream Appium catalog and adds two non-blocking lifecycle tools:
appium_prepare_ios_real_device_asyncappium_create_session_async
Both support start, status, and cancel. Safari session requests use a private, persistent FIFO waiting room. Only one preparation or owned session may run across local bridge processes, while up to 20 Safari requests may wait in the managed bridge runtime.
Related MCP server: iPhone MCP Server
Requirements
macOS with Xcode 16 or newer
Node.js 24 or newer
a paired, trusted iPhone with Developer Mode enabled
Safari Web Inspector and Remote Automation enabled
an Apple Development identity and suitable WDA provisioning profile
OpenAI Secure MCP Tunnel access with Tunnels Read + Use
ChatGPT developer-mode app access
Cellular mode additionally needs a Cloudflare Workers account, iOS 17 or newer, and one initial USB install of the Bridge Browser app from Xcode.
Official tunnel documentation: OpenAI Secure MCP Tunnel.
Install
bash scripts/bootstrap-local.shBootstrap uses the committed lockfile, applies the reviewed dependency patches, checks Xcode, and runs the direct MCP contract. It refuses to replace dependencies while its managed runtime is active.
If signing is missing, add your Apple ID under Xcode -> Settings -> Accounts, then create the WDA runner profile:
IOS_DEVICE_UDID=<connected-udid> \
DEVELOPMENT_TEAM=<apple-team-id> \
bash scripts/prepare-ios-signing.shThe private preparation worker renews an expired device-bound .xctrunner development profile with Xcode automatic signing before it signs WDA. It uses the existing profile's team and bundle ID, requires the selected iPhone to be available to Xcode, and never returns Xcode account details in MCP errors.
Device IDs, team IDs, profile UUIDs, signed WDA files, screenshots, and runtime keys must stay outside the repository.
Connect
Store the runtime key in a user-owned mode-600 file:
mkdir -p "$HOME/.config/chatgpt-iphone-bridge"
chmod 700 "$HOME/.config/chatgpt-iphone-bridge"
umask 077
read -rs CONTROL_PLANE_KEY
printf '%s' "$CONTROL_PLANE_KEY" > "$HOME/.config/chatgpt-iphone-bridge/runtime-api-key"
unset CONTROL_PLANE_KEY
chmod 600 "$HOME/.config/chatgpt-iphone-bridge/runtime-api-key"Connect a dedicated tunnel:
CONTROL_PLANE_TUNNEL_ID=tunnel_... bash scripts/connect-tunnel.shThe default managed alias is local-iphone-bridge. Connect refuses to replace a running alias that targets another launcher and rolls back a runtime that it starts but cannot make ready.
Create a ChatGPT developer-mode app named Local iPhone, choose Tunnel, select this dedicated tunnel, and use No Auth for the MCP app. Workspace and tunnel access therefore equal temporary control of the unlocked phone.
ChatGPT workflow
Call
select_devicewithplatform=iosandiosDeviceType=real. When Xcode reports exactly one host-attached iPhone, the bridge prefers it over paired network ghosts; an explicitdeviceUdidstill wins.Call
appium_prepare_ios_real_device_asyncwithaction=startand the selected UDID.Poll
action=statuswith the returnedoperationId. Pick a recommended profile from the discovery result.Start preparation again with that profile UUID and poll with its
operationIduntilstate=ready. Selecting the same iPhone again preserves this shared ready preparation.Combine the returned
capabilitiesHintwith:
{
"browserName": "Safari",
"appium:safariInitialUrl": "https://example.test/"
}Call
appium_create_session_asyncwithaction=start, the capabilities, and a uniqueclientRequestId. Reuse that client request ID only when retrying the same start call.Keep the returned
operationIdprivate. Pollaction=statuswith it untilstate=ready. A waiting response includes its one-based position, queue depth, reason, heartbeat deadline, and recommended poll interval. Every waiting status call renews the 10-minute heartbeat.Use
action=cancelwith the same operation ID to leave the queue or clean up an active request.Use normal Appium interaction tools with the returned session.
Delete the owned session when finished so the next live request can start.
Queued requests survive a managed bridge restart in FIFO order. A restored request must send one fresh status heartbeat before it can start. Work that was already starting or active is marked interrupted because Appium session survival cannot be proven.
Waiting requests expire after ten minutes without a status heartbeat and cannot be revived. Active sessions have no automatic expiry.
Before creation, the bridge verifies that the selected iPhone is unlocked. Preinstalled WDA gets a 60-second launch window and one internal retry after a clean launch failure; the same async operation and clientRequestId remain in use. Terminal failures distinguish DEVICE_LOCKED, DEVICE_STATE_UNAVAILABLE, WDA_LAUNCH_FAILED, and LIFECYCLE_TIMEOUT.
Blocking preparation and creation, remote Appium URLs, session attachment, simulators, Android, native apps, and unprepared WDA paths fail closed.
Privileged tools are disabled by default. Enable only named tools locally:
APPIUM_BRIDGE_PRIVILEGED_TOOLS=appium_mobile_clipboard,appium_geolocation \
CONTROL_PLANE_TUNNEL_ID=tunnel_... \
bash scripts/connect-tunnel.shSet APPIUM_BRIDGE_UNSAFE_FULL_APPIUM=true only in a fully trusted local environment.
Operations
npm test
npm run status
npm run queue:status
npm run runtime:monitor:install
npm run runtime:monitor:status
npm run runtime:repair
npm run doctor
npm run prune
bash scripts/stop.shstatusis fast and redacted.queue:statusis local-only and shows the redacted FIFO order and private operation handles without capabilities, URLs, device IDs, or session IDs.runtime:monitor:installinstalls an alert-only user LaunchAgent that checks the canonical runtime every 60 seconds. Install it only from the stable checkout path; it never reconnects automatically and can be removed withnpm run runtime:monitor:uninstall.runtime:monitor:statuschecksprocess_running,healthy,ready, and either owned USB-only or cellular launcher identity without changing runtime state.runtime:repairis the only monitor-related reconnect path. It reuses the canonical alias and stored tunnel ID, validates the mode-600runtime key, and refuses another launcher. Repairing cellular mode requires the sameIPHONE_BRIDGE_CELLULAR_*environment used to connect it.doctorchecks the toolchain, MCP contract, model-based real-device presence, signing, and managed runtime. A user-defined device name does not affect detection.pruneremoves screenshots older than seven days; override withAPPIUM_BRIDGE_RETENTION_DAYS.stopis idempotent and refuses to stop an alias that targets another launcher.
Cellular Bridge Browser
This zero-cost mode lets the target iPhone leave the Mac and use cellular data. The Mac must stay powered on and connected to the existing Secure MCP Tunnel. The user must manually open Bridge Browser, approve the requested HTTPS origins, and keep the app in the foreground.
Cloudflare Durable Objects are available on the Workers Free plan. If the free quota is exhausted, the relay fails closed. No public port is opened on the Mac.
1. Deploy the relay
npm ci --prefix relay
npm exec --prefix relay -- wrangler login
npm run cellular:deploySave the deployed https://...workers.dev URL. The relay uses a SQLite-backed Durable Object and hibernating WebSockets. Application logging is disabled.
2. Install the free iPhone app
Connect the iPhone once, select a unique bundle ID, and use the Personal Team shown in Xcode:
IOS_DEVICE_UDID=<connected-udid> \
DEVELOPMENT_TEAM=<personal-team-id> \
BRIDGE_BROWSER_BUNDLE_ID=com.example.myiphonebridge \
npm run cellular:ios:installA free Personal Team provisioning profile expires after seven days. Rerun the install command every week. This setup does not use TestFlight or push notifications.
3. Pair the phone
export IPHONE_BRIDGE_CELLULAR_RELAY_URL=https://your-relay.workers.dev
export IPHONE_BRIDGE_CELLULAR_IDENTITY_FILE="$HOME/.config/chatgpt-iphone-bridge/cellular-host.json"
npm run cellular:pairScan the displayed QR in Bridge Browser, or paste the printed pairing payload. It expires after five minutes. The host identity is written outside the repository with mode 600.
If the terminal QR is not visible, set IPHONE_BRIDGE_PAIRING_QR_FILE to an absolute PNG path before running cellular:pair. The QR file is written mode 600; delete it after pairing.
4. Enable cellular tools
Pass the opt-in configuration when connecting the existing tunnel:
IPHONE_BRIDGE_CELLULAR_ENABLED=true \
IPHONE_BRIDGE_CELLULAR_RELAY_URL=https://your-relay.workers.dev \
IPHONE_BRIDGE_CELLULAR_IDENTITY_FILE="$HOME/.config/chatgpt-iphone-bridge/cellular-host.json" \
CONTROL_PLANE_TUNNEL_ID=tunnel_... \
bash scripts/connect-tunnel.shThe cellular and USB-only modes use distinct managed launcher identities. If the USB-only runtime is already active, stop it with bash scripts/stop.sh before connecting cellular mode. The bridge refuses to silently reuse a runtime started in the other mode.
Disabled mode still exposes exactly the original 33 Appium tools. Enabled mode adds seven separate tools:
iphone_browser_device_statusiphone_browser_sessioniphone_browser_navigateiphone_browser_findiphone_browser_elementiphone_browser_snapshotiphone_browser_screenshot
Start iphone_browser_session with an HTTPS initialUrl and explicit allowedOrigins. Poll while the phone is closed. Open Bridge Browser, review the origins, tap Approve, and keep the app foreground. Stop the session when finished.
Only approved HTTPS top-level origins are allowed. Downloads, custom URL schemes, file URLs, arbitrary remote JavaScript, media permissions, native apps, Safari, and unattended background control are not supported.
Example: mobile gameplay testing
Bridge Browser can test a browser-based mobile game through the same touch-oriented UI that a player sees on the iPhone. ChatGPT can locate DOM controls, tap a context button, press and hold a throttle, drag a joystick within normalized element coordinates, read HUD text, and capture screenshot checkpoints. The iPhone may use Wi-Fi or cellular, but Bridge Browser must remain open in the foreground.
For example, the following prompt tests the public GTA Labin build without USB or Appium:
Use only the Local iPhone iphone_browser_* tools. Do not use Appium, Safari,
select_device, or arbitrary JavaScript.
1. Call iphone_browser_device_status and require secureReady=true.
2. Start iphone_browser_session for:
initialUrl: https://kapunakap.github.io/gta-labin/
allowedOrigins: ["https://kapunakap.github.io"]
3. Keep polling the same operation. Ask me to open Bridge Browser and tap
Approve when the native approval card appears. Do not cancel it.
4. When ready, take a snapshot and confirm LABIN 52220 is visible.
5. Find CSS [data-touch-action=context], tap it, refind it, and verify its
text changes from ENTER to EXIT.
6. Find CSS [data-touch-action=gas] and press it for 1500 ms at x=0.5,y=0.5.
7. Find CSS [data-touch-stick=move] and drag from x=0.5,y=0.5 to
endX=0.8,endY=0.5 for 700 ms.
8. Capture a snapshot and screenshot. Report the visible speed/distance and
whether it changed from the previous checkpoint.
9. Stop the exact session and require state=closed and cleanupPending=false.iphone_browser_element supports tap, press, and drag without adding another MCP tool. A press is bounded to 10 seconds. Drag coordinates are relative to the found element: 0,0 is its top-left and 1,1 is its bottom-right. Refind an element after the page replaces it or navigation changes the document.
This workflow is suitable for smoke tests, HUD assertions, menus, virtual buttons, and short movement checkpoints. It does not turn Bridge Browser into native-app automation. Games that require hardware buttons, native APIs, trusted OS gestures, pointer lock, or controls that are not exposed through the page may still need the separate USB/Appium physical QA harness.
Cellular operations
npm run cellular:status
npm run cellular:doctor
npm run cellular:revoke
npm run cellular:ios:checkstatus and doctor are redacted. revoke invalidates both relay credentials and removes the local host credential. Pair again before re-enabling cellular mode.
Local tests and an unsigned iOS build do not prove cellular acceptance. Before release, verify the full flow through hosted ChatGPT with the iPhone unplugged, Wi-Fi disabled, and no active Appium session.
Local physical Bridge Browser QA
The local-only harness launches only com.kapunakap.chatgptiphonebridge.BridgeBrowser. It refuses Safari and other apps, uses one persistent local Appium MCP connection, and never uses the OpenAI tunnel. Its unsafe Appium policy bypass exists only in that child process.
Stop the managed tunnel first, keep the paired iPhone unlocked, then run:
IPHONE_BRIDGE_CELLULAR_ENABLED=true \
IPHONE_BRIDGE_CELLULAR_RELAY_URL=https://your-relay.workers.dev \
IPHONE_BRIDGE_CELLULAR_IDENTITY_FILE="$HOME/.config/chatgpt-iphone-bridge/cellular-host.json" \
npm run qa:bridge-browser:physicalUse -- --diagnose-only to capture the native connection screen and require hostOnline, deviceOnline, and secureReady without starting GTA Labin. Full runs retain private screenshots and route checkpoints under ignored artifacts/bridge-browser-physical-qa/. Success requires WebGPU, CREATE, ENTER, sustained physical GAS/BRAKE/steering actions, route completion, background close/reconnect, zero Appium sessions, and no remaining lease directory. Any partial run is a failure and does not count as Safari acceptance.
Neutral Safari fixture
The repository includes a small generic page for simulator and physical-device acceptance:
npm run fixtureIt binds to loopback by default. To make it reachable from an iPhone on a trusted LAN:
FIXTURE_HOST=0.0.0.0 npm run fixtureDo not expose the fixture beyond the intended test network.
Physical smoke probes the controlled page before consuming an iPhone session. If local VPN or firewall policy blocks inbound LAN HTTP, configure an explicit neutral HTTPS fallback:
BRIDGE_FIXTURE_URL=http://192.168.1.10:4173/ \
BRIDGE_FIXTURE_FALLBACK_URL=https://example.com/ \
BRIDGE_FIXTURE_FALLBACK_SELECTOR=h1 \
BRIDGE_FIXTURE_FALLBACK_MARKER='Example Domain' \
npm run smoke:physicalThe smoke output reports fixture_source=controlled or fixture_source=fallback; it never silently substitutes a page.
ChatGPT app release gate
npm run smoke verifies that the live MCP tool schema contains clientRequestId. After any tool name, description, or input-schema change, refresh Local iPhone under ChatGPT plugin settings and open a fresh chat. Do not declare the update complete until the managed schema also shows clientRequestId and a ChatGPT-native physical Safari run ends with a screenshot, zero sessions, and an empty queue. Add required arguments only through a compatibility window or a versioned tool.
Extension API
External packages can compose additional Appium plugins without copying the lifecycle implementation:
import { startIphoneBridgeServer } from "chatgpt-iphone-bridge/server";
await startIphoneBridgeServer({
plugins: [myPlugin],
serverName: "My Local iPhone Tools",
policy: { allowTools: [/^(?!prepare_ios_simulator$).*$/] }
});External packages can enable the paired cellular browser with cellular: { enabled: true, relayUrl, identityPath }; it remains disabled when this option and the matching environment flag are absent.
Consumers should install an exact released package version instead of a floating range.
Security
This bridge can control a real unlocked phone and signed-in Safari sessions. Read SECURITY.md before connecting it.
License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP connector that lets ChatGPT list, search, and run your Apple Shortcuts via a local Mac agent
A paid remote MCP for AI agent browser DevTools MCP, built to return verdicts, receipts, usage logs,
Live browser debugging for AI assistants — DOM, console, network via MCP.
Control real Android and iOS devices with LLM agents — tap, swipe, type, automate flows.
Related MCP Servers
- AlicenseBqualityDmaintenanceProvides AI assistants with Safari browser automation and developer tools access, enabling LLMs to control Safari, access console logs, monitor network activity, and perform browser automation tasks.139 npm33MIT
- AlicenseNot gradedqualityDmaintenanceEnables iPhone automation through Appium integration, allowing users to control apps, capture screenshots, interact with UI elements, and perform touch operations via natural language commands.135MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI to control iOS simulators through the MCP protocol. Supports device management, UI automation, and network interception including screenshot capture, text input, and HTTP request mocking.-
- AlicenseAqualityCmaintenanceMCP server that drives Safari on a physically attached iPhone via Apple's safaridriver, enabling navigation, screenshots, DOM snapshots, console logs, network timings, and tap/type/scroll actions on the real device. No Xcode, Appium, or WebDriverAgent required.16MIT