pulse-mcp-bridge
Allows interaction with an Expo mobile app to capture live device status, app logs, screenshots, and bug reports.
Provides integration with local Git repositories to retrieve today's commits for standup snapshots combined with device status.
Allows interaction with a React Native mobile app to retrieve device status, logs, screenshots, and bug reports.
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., "@pulse-mcp-bridgewhat's the device status?"
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.
pulse-mcp-bridge
An MCP (Model Context Protocol) server that bridges your IDE to a running React Native / Expo mobile app on your local Wi-Fi network, so tools like Claude Code or Cursor can pull live device status, app logs, automatically-captured crashes (with stack traces and breadcrumbs, no manual instrumentation required), and a combined standup snapshot (device status + today's git commits).
How it fits together
IDE (MCP client) <--stdio--> pulse-mcp (index.js) <--HTTP--> mobile app (mobile-app-server.js)
or <--HTTP--> mock-phone.js (for local testing)Related MCP server: React Native Debug MCP
Setup
npm install1. Try it locally with the mock phone (no device needed)
In one terminal, start the mock phone server:
node mock-phone.jsThis listens on http://localhost:8080 and fakes /status and /logs responses.
2. Register the MCP server with your IDE
See mcp-config.example.json for ready-to-paste config blocks for Cursor, Claude Desktop, and Windsurf. Point MOBILE_PHONE_IP at:
127.0.0.1when testing againstmock-phone.jsyour phone's local IP (e.g.
192.168.1.50) when testing against a real device
3. Wire up the real mobile app
index.js (the MCP server) is generic and reusable as-is — it never needs to change per app. The only thing that changes per app is mobile-app-server.js, the tiny HTTP bridge that runs inside the app.
Setup, once per new app:
Copy the bridge file into the target app, e.g. as
src/pulseServer.js:cp mobile-app-server.js /path/to/your-app/src/pulseServer.jsInstall its native dependencies in that app:
npx expo install react-native-http-bridge-refurbished react-native-device-info react-native-view-shot @react-native-async-storage/async-storageThese are native modules — if the app runs on plain Expo Go, you'll need a dev-client build instead (
npx expo prebuildthennpx expo run:ios/npx expo run:android).Wire it into
App.tsx:import { startPulseServer, setCurrentRoute, recordLog } from "./pulseServer"; useEffect(() => { if (__DEV__) startPulseServer(); }, []); // On navigation change: setCurrentRoute(routeName); // Anywhere in the app: recordLog("Synced dashboard data", "success", "SyncService"); recordLog(`Network request failed: ${error.message}`, "error", "NetworkClient");Add the bug-report recording hooks too if you want
get_bug_report/get_saved_bug_reports— see "Bug-report step recording" below.Crash capture needs no extra wiring at all beyond
startPulseServer()— see "Automatic crash capture" below.Same Wi-Fi network — your phone/emulator and dev machine must be on the same Wi-Fi network (or use
127.0.0.1for the iOS Simulator, which shares the Mac's own network stack).Point your MCP config at this device: set
MOBILE_PHONE_IP(in your IDE's MCP config, seemcp-config.example.json) to that device's IP —127.0.0.1for Simulator, the phone's LAN IP (e.g.192.168.1.50) for a real device.
Real device notes:
Real iPhone — verified. This integration has been tested end-to-end against a real Expo/React Native app on a real iPhone, including a live crash scenario — the bridge correctly goes unreachable when the app stops responding. iOS 14+ will prompt for a one-time "Local Network" permission the first time the app starts the bridge server — the user must tap Allow, or the bridge will be unreachable.
Real Android — not yet verified, though it should work in principle (
react-native-http-bridge-refurbishedsupports Android too). Android 9+ blocks plaintext HTTP by default, so you'll likely needandroid:usesCleartextTraffic="true"inAndroidManifest.xmlfor local dev builds, or the bridge will silently fail to connect.Android emulator: since it's NAT'd and can't be reached directly, use
127.0.0.1plusadb forward tcp:8080 tcp:8080.
Running demo-app on a real iPhone
demo-app/ios is checked in with manual signing (CODE_SIGN_STYLE = Manual, provisioning profile IndiaNIC-WildCard-Development, team 7BCW99KL8P, certificate Apple Development: Manish Manish) — this is intentional, not a misconfiguration, so don't "fix" it by switching to Automatic. npx expo run:ios --device <udid> doesn't handle manual signing well, so build/install directly with the Apple toolchain instead:
cd demo-app/ios
xcrun xctrace list devices # find your device's UDID (must show under "Devices", not "Devices Offline" — plug in + unlock + trust first)
xcodebuild -workspace demoapp.xcworkspace -scheme demoapp -configuration Debug \
-destination "id=<UDID>" build
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData/demoapp-*/Build/Products/Debug-iphoneos -maxdepth 1 -name "*.app")
xcrun devicectl device install app --device <UDID> "$APP_PATH"
xcrun devicectl device process launch --device <UDID> com.indianic.pulseIf xcodebuild errors with conflicting provisioning settings or No profiles for 'com.indianic.pulse' were found, the project's signing got out of sync with Xcode's UI state — open demoapp.xcodeproj in Xcode, go to the demoapp target's Signing & Capabilities tab, and re-select the IndiaNIC-WildCard-Development profile there (this rewrites project.pbxproj correctly). Don't hand-edit project.pbxproj signing fields — let Xcode do it.
Available MCP tools
Tool | Description |
| Battery level, active route/screen, OS/platform, and |
| Recent structured log entries ( |
| Pings |
| Diagnoses the most recent problem, preferring a captured crash (with its real stack trace and breadcrumbs) over a plain log message when both exist. Returns a structured |
| Combines live device status with today's local git commits (falls back gracefully outside a git repo) |
| Live screenshot of whatever screen is currently showing on the app, returned as an inline image |
| The current/live step recording (see below) plus device context, for reproducing a bug in progress |
| All bug reports auto-saved on the device, each with its full steps, logs, and device info snapshot |
| All crashes automatically captured on the device (see "Automatic crash capture" below), each with a stack trace, breadcrumb trail, and a best-guess |
Example prompts
Copy-paste any of these into Claude to trigger the matching tool:
Tool | Example prompt |
| "Check my mobile connection" |
| "What's the current status of my device?" |
| "Show me the recent app logs from my phone." |
| "Show me any crash logs from the mobile app." |
| "Take a screenshot of my phone's current screen." |
| "The app crashed on the login screen, can you diagnose why?" |
| "Get the current bug report / step recording." |
| "List all saved bug reports." |
| "Give me a standup snapshot." |
Bug-report step recording
Wire a "Start/Stop Recording" toggle in your app to startSessionRecording() / stopSessionRecording(), and call recordStep(...) at any point worth capturing (button taps, network results, errors). When recording stops, a complete report — numbered steps, full logs, and a device/build info snapshot — is auto-saved and instantly queryable via get_bug_report / get_saved_bug_reports. No manual "steps to reproduce" writeup needed.
Automatic crash capture
Unlike bug-report recording, this needs no toggle and no manual recordLog/recordStep calls at all — it's live the moment startPulseServer() runs, and catches three distinct failure paths that would otherwise vanish without a trace:
Failure path | Caught by |
|
Uncaught exception in an event handler or async code |
|
|
A crash during React's render phase | A root |
|
A rejected promise nobody | Hermes's rejection tracker (falls back to the |
|
Every crash automatically includes a breadcrumb trail — the last ~20 things that happened right before it, built with zero developer effort:
every navigation change (from
setCurrentRoute())every
fetch()call your app makes, success or failure (global.fetchis patched transparently; requests tolocalhost/Metro's own dev tooling are excluded)
Crashes survive the app dying. The instant a crash is captured, it's written to AsyncStorage before anything else runs. If the crash kills the whole JS engine before the in-memory list or an HTTP request could reflect it, it's recovered automatically the next time the app launches (tagged recoveredFromDisk: true) and cleared from disk so it isn't re-flushed again.
This is what makes get_mobile_crash_logs/diagnose_mobile_error answer "why did my app crash" for a crash that happened five minutes ago and killed the app, not just one you're actively watching for.
Remote / cloud relay mode
Everything above requires the phone and this MCP server to be on the same Wi-Fi network (MOBILE_PHONE_IP). If your tester/client's device is somewhere else entirely — a different city, a different network, cellular data — use the relay instead: the phone pushes its state to an always-on hosted service, and index.js reads from that service over HTTPS instead of a LAN IP.
When to use it: the developer and the device are not on the same network, or you want crashes/logs captured even when no one's MCP client happens to be running at that moment.
Setup:
Deploy
relay-server/somewhere always-on — click Deploy to Render inrelay-server/README.mdfor a one-click setup (auto-generatesPULSE_API_KEYand provisions the persistent disk via the includedrender.yaml), or follow the manual steps there.In the mobile app, alongside
startPulseServer(), call:import { configurePulseRelay } from "./pulseServer"; configurePulseRelay({ url: "https://your-relay.onrender.com", apiKey: "the-same-PULSE_API_KEY-as-the-relay", deviceId: "some-stable-id-for-this-device", // e.g. a UUID you generate once and persist });On the MCP server side, set
PULSE_RELAY_URL,PULSE_API_KEY, andPULSE_DEVICE_ID(matching the values above) instead ofMOBILE_PHONE_IP— see therelay_mode_exampleblock inmcp-config.example.json.
All 9 tools work identically in relay mode — index.js's tool handlers don't know or care whether they're talking to the phone directly or through the relay.
Security note: the relay is internet-exposed, unlike the trusted-LAN-only local bridge. Every request requires the x-pulse-api-key header; treat that key like a password (don't commit it, rotate it if it leaks). There's no per-device auth beyond the shared key and deviceId, so this is meant for a small number of trusted testers/devices, not a public multi-tenant service.
Automated crash-fix pipeline (optional): the relay can also automatically kick off a Claude Code agent on every new crash, bug report, or a free-text command typed into the app's "Ask IDE" screen, which opens a PR with a candidate fix for you to review. This is opt-in — set GITHUB_TOKEN/GITHUB_REPO on the relay and add ANTHROPIC_API_KEY to this repo's GitHub Actions secrets to enable it (see .github/workflows/auto-fix-crash.yml, relay-server/lib/githubDispatch.js, and mobile-app-server.js's sendUserCommand()). Leave those unset and the relay works exactly the same, just without the auto-fix step.
Sharing this with someone else, without sharing the source code
If someone else (a friend, another team) wants to use the MCP tools against their own app, but you don't want to hand them this repo, deploy hosted-server.js instead of giving them index.js:
It's the same 9 tools, but served over HTTP (
StreamableHTTPServerTransport) instead of stdio, so it can be deployed once and reused by anyone you give the URL to — no code, no local install on their end.It's multi-tenant: each caller supplies their own relay URL/key/deviceId via request headers (
X-Pulse-Relay-Url,X-Pulse-Relay-Api-Key,X-Pulse-Device-Id), plus a sharedAuthorization: Bearer <HOSTED_ACCESS_KEY>just to reach the server at all. Nothing is persisted or shared between callers — seehosted-server.js's top comment for the exact contract.Each person still needs their own relay (deployed with the one-click Render button above) and their own
mobile-app-server.jsin their own app — that part can't be skipped, since it's what exposes their app's data, but it requires zero source code from you, just the URL + access key.
Deploy it the same way as the relay (Node service, set HOSTED_ACCESS_KEY), then their IDE's MCP config is just:
{
"mcpServers": {
"pulse-mcp": {
"url": "https://your-hosted-server.example.com/mcp",
"headers": {
"Authorization": "Bearer <HOSTED_ACCESS_KEY>",
"X-Pulse-Relay-Url": "https://their-own-relay.onrender.com",
"X-Pulse-Relay-Api-Key": "their-own-relay-PULSE_API_KEY",
"X-Pulse-Device-Id": "their-own-deviceId"
}
}
}
}Note: get_standup_snapshot's git-log feature returns a "not available" message in hosted mode, since "today's local commits" has no meaning for a shared remote server reading its own git history.
Current status / progress
Snapshot of what's built and verified so far, and what's still open.
Working end-to-end (verified live):
Cloud relay mode — status/logs/crashes/reports/session pushed from a real device through a cloudflared tunnel and read back via
index.js, including with the phone on cellular data (proves the cross-network case).On-demand screenshot round trip (enqueue → phone poll → fulfill → response).
The "Ask IDE" command box in the demo app → relay's
/devices/:id/user-commandsendpoint → GitHubrepository_dispatch→ theauto-fix-crash.ymlworkflow actually running (checkout, branch creation, Claude Code CLI install all succeed).
Blocked — pending a funded Anthropic API key: the workflow's claude -p step needs ANTHROPIC_API_KEY in this repo's GitHub Actions secrets. A key was added but the run failed with Credit balance is too low — the Console account behind that key has no billing/credits yet. Add a payment method / credits at https://console.anthropic.com/settings/billing (or provide a different key with balance), then re-trigger a test command to confirm a PR gets opened.
Explored and abandoned: using ant auth login (OAuth tied to a Claude Pro/Max plan) instead of a billed API key, to avoid CI usage counting against API credits. Works for interactive login, but isn't a supported flow for unattended CI (refresh-token handling, and it ties a personal account credential to the pipeline) — reverted in favor of a plain ANTHROPIC_API_KEY secret.
Not yet done:
Deploying
relay-server/to Render (or another always-on host) — it's currently only verified running locally + tunneled, not on permanent infrastructure.Per-device repo mapping for the auto-fix pipeline, needed if this bridge is ever used across more than one project's repo (currently hardcoded to
tarunmalpani/pulse-mcp-bridge).
Environment variables
Variable | Default | Purpose |
|
| IP address of the phone/mock server running the HTTP bridge (local Wi-Fi mode only) |
| unset | Base URL of the relay server (see "Remote / cloud relay mode"); when set, this replaces |
| unset | Shared secret sent as |
| unset | Which device's state to read from the relay (must match the |
This server cannot be deployed
Maintenance
Related MCP Connectors
Drive real devices from your AI Coding tool. Embed a client SDK (Unity, Godot, Flutter, iOS/macOS, Android, React Native, Web) in your app, then capture screenshots, traverse the UI tree, inject taps and key events, and run automated test tasks on the physical device over a secure relay.
remote debug iOS/Android/Unity/Godot/Flutter/RN/Web on real-device.ui-tree/screenshots/taps,tests.
Run App Store Connect from your IDE: pricing, listings, screenshots, releases, AI visibility.
Your Expo and EAS project in natural language: up to date SDK docs, cloud builds (status, logs, trig
Related MCP Servers
- AlicenseBqualityAmaintenanceCaptures and provides access to React Native console logs from Metro bundler, enabling AI assistants to retrieve, filter, and search app logs in real-time without manual copy/paste.641,546 npm75MIT
- AlicenseBqualityDmaintenanceEnables AI agents to inspect, log, and control React Native apps on Android emulators or iOS simulators, including UI tree, tap, scroll, and hot reload.22557 npm5MIT
- AlicenseNot gradedqualityDmaintenanceBridges React Native DevTools, enabling AI assistants to debug, profile, and inspect React Native applications through a standardized protocol.16 npm4MIT
- AlicenseNot gradedqualityAmaintenanceA plugin-based MCP server for React Native runtime debugging, inspection, and automation via Chrome DevTools Protocol. Works with Expo, bare React Native, and any Metro + Hermes project without app code changes.818 npm79MIT