expo-agent-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js ADDED
@@ -0,0 +1,276 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startAgentBridgeMcpServer = startAgentBridgeMcpServer;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const zod_1 = require("zod");
7
+ function startAgentBridgeMcpServer(options = {}) {
8
+ const PLUGIN_NAME = options.pluginName ?? process.env.EXPO_AGENT_BRIDGE_PLUGIN ?? 'expo-agent-bridge';
9
+ const METRO_PORT = options.metroPort ?? Number(process.env.EXPO_METRO_PORT ?? 8081);
10
+ const PROTOCOL_VERSION = 1;
11
+ const COMMAND_TIMEOUT_MS = options.commandTimeoutMs ?? 30000;
12
+ let commandSeq = 0;
13
+ const pendingCommands = new Map();
14
+ // ─── In-memory log buffer ───────────────────────────────────────────────────
15
+ const recentLogs = [];
16
+ const MAX_LOGS = 100;
17
+ function addLog(entry) {
18
+ if (!entry)
19
+ return;
20
+ recentLogs.push(entry);
21
+ if (recentLogs.length > MAX_LOGS)
22
+ recentLogs.shift();
23
+ }
24
+ // ─── DevTools Plugin WebSocket connection ───────────────────────────────────
25
+ let ws = null;
26
+ let wsReady = false;
27
+ let connectionPromise = null;
28
+ const browserClientId = Date.now().toString();
29
+ function packMessage(method, payload) {
30
+ const messageKey = { pluginName: PLUGIN_NAME, method };
31
+ return JSON.stringify({ messageKey, payload });
32
+ }
33
+ function unpackMessage(data) {
34
+ if (typeof data === 'string')
35
+ return JSON.parse(data);
36
+ return null;
37
+ }
38
+ function sendHandshake() {
39
+ ws.send(JSON.stringify({
40
+ __isHandshakeMessages: true,
41
+ protocolVersion: PROTOCOL_VERSION,
42
+ pluginName: PLUGIN_NAME,
43
+ method: 'handshake',
44
+ browserClientId,
45
+ }));
46
+ }
47
+ function connectToMetro() {
48
+ if (connectionPromise)
49
+ return connectionPromise;
50
+ connectionPromise = new Promise((resolve, reject) => {
51
+ const url = `ws://localhost:${METRO_PORT}/expo-dev-plugins/broadcast`;
52
+ process.stderr.write(`[expo-agent-bridge] Connecting to ${url}\n`);
53
+ ws = new WebSocket(url);
54
+ ws.addEventListener('open', () => {
55
+ wsReady = true;
56
+ sendHandshake();
57
+ process.stderr.write('[expo-agent-bridge] Connected to DevTools broadcast channel ✓\n');
58
+ resolve();
59
+ });
60
+ ws.addEventListener('message', (event) => {
61
+ const raw = event.data;
62
+ let msg;
63
+ try {
64
+ msg = unpackMessage(typeof raw === 'string' ? raw : raw.toString());
65
+ }
66
+ catch {
67
+ return;
68
+ }
69
+ if (!msg)
70
+ return;
71
+ if (msg.__isHandshakeMessages)
72
+ return;
73
+ const { messageKey, payload } = msg;
74
+ if (!messageKey || messageKey.pluginName !== PLUGIN_NAME)
75
+ return;
76
+ // Real-time log/error streamed from app
77
+ if (messageKey.method === 'log') {
78
+ addLog(payload);
79
+ return;
80
+ }
81
+ if (messageKey.method !== 'result')
82
+ return;
83
+ const pending = pendingCommands.get(payload?.id);
84
+ if (!pending)
85
+ return;
86
+ clearTimeout(pending.timer);
87
+ pendingCommands.delete(payload.id);
88
+ if (payload.error) {
89
+ pending.reject(new Error(payload.error));
90
+ }
91
+ else {
92
+ pending.resolve(payload);
93
+ }
94
+ });
95
+ ws.addEventListener('close', () => {
96
+ wsReady = false;
97
+ connectionPromise = null;
98
+ ws = null;
99
+ process.stderr.write('[expo-agent-bridge] DevTools connection closed — will reconnect on next tool call\n');
100
+ for (const [id, pending] of pendingCommands) {
101
+ clearTimeout(pending.timer);
102
+ pending.reject(new Error('Connection closed while waiting for phone response'));
103
+ pendingCommands.delete(id);
104
+ }
105
+ });
106
+ ws.addEventListener('error', (e) => {
107
+ process.stderr.write(`[expo-agent-bridge] WS error: ${e?.message ?? 'connection failed'}\n`);
108
+ connectionPromise = null;
109
+ reject(e);
110
+ });
111
+ });
112
+ return connectionPromise;
113
+ }
114
+ async function sendCommand(action, params = {}) {
115
+ try {
116
+ await connectToMetro();
117
+ }
118
+ catch (e) {
119
+ throw new Error(`Cannot connect to Metro dev server on port ${METRO_PORT}. ` +
120
+ `Make sure 'npx expo start' is running. (${e.message})`);
121
+ }
122
+ if (!wsReady) {
123
+ throw new Error('DevTools connection not ready. Is expo running and the dev app open on phone/simulator?');
124
+ }
125
+ // The app-side DevTools listener needs a brief moment after the broadcast
126
+ // handshake before it can reliably receive the first command. The direct
127
+ // CLI uses the same delay; keeping both transports aligned avoids a
128
+ // first-call race in MCP clients.
129
+ await new Promise((resolve) => setTimeout(resolve, 250));
130
+ const id = String(++commandSeq);
131
+ const cmd = { id, action, ...params };
132
+ return new Promise((resolve, reject) => {
133
+ const timer = setTimeout(() => {
134
+ pendingCommands.delete(id);
135
+ const lastError = recentLogs.slice().reverse().find((l) => l.level === 'error');
136
+ let msg = `Timed out after ${COMMAND_TIMEOUT_MS / 1000}s waiting for phone response. Is the dev app open?`;
137
+ if (lastError) {
138
+ msg += `\n[Recent App Error]: ${lastError.message}`;
139
+ if (lastError.stack)
140
+ msg += `\n${lastError.stack}`;
141
+ }
142
+ reject(new Error(msg));
143
+ }, COMMAND_TIMEOUT_MS);
144
+ pendingCommands.set(id, { resolve, reject, timer });
145
+ ws.send(packMessage('command', cmd));
146
+ });
147
+ }
148
+ function triggerMetroReload() {
149
+ return new Promise((resolve) => {
150
+ try {
151
+ const reloadWs = new WebSocket(`ws://localhost:${METRO_PORT}/message`);
152
+ reloadWs.addEventListener('open', () => {
153
+ reloadWs.send(JSON.stringify({ version: 2, method: 'reload' }));
154
+ setTimeout(() => {
155
+ try {
156
+ reloadWs.close();
157
+ }
158
+ catch { }
159
+ resolve();
160
+ }, 300);
161
+ });
162
+ reloadWs.addEventListener('error', () => resolve());
163
+ }
164
+ catch {
165
+ resolve();
166
+ }
167
+ });
168
+ }
169
+ // ─── MCP Server ─────────────────────────────────────────────────────────────
170
+ const mcp = new mcp_js_1.McpServer({ name: 'expo-agent-bridge', version: '0.1.0' });
171
+ mcp.tool('get_screenshot', 'Capture the current mobile screen as a PNG. Returns base64 image data. If the app crashed or timed out, recent error logs will be included.', {}, async () => {
172
+ const res = await sendCommand('screenshot');
173
+ if (!res.data)
174
+ throw new Error('No image data returned from app.');
175
+ return { content: [{ type: 'image', data: res.data, mimeType: 'image/png' }] };
176
+ });
177
+ mcp.tool('get_logs', 'Get recent console errors, warnings, and unhandled exceptions streamed from the app.', {
178
+ level: zod_1.z.enum(['error', 'warn', 'all']).optional().describe('Filter by log level (default: "all")'),
179
+ limit: zod_1.z.number().optional().describe('Maximum entries to return (default: 30)'),
180
+ }, async ({ level = 'all', limit = 30 }) => {
181
+ let logs = recentLogs;
182
+ if (level === 'error')
183
+ logs = logs.filter((l) => l.level === 'error');
184
+ else if (level === 'warn')
185
+ logs = logs.filter((l) => l.level === 'error' || l.level === 'warn');
186
+ const entries = logs.slice(-limit);
187
+ if (entries.length === 0) {
188
+ return { content: [{ type: 'text', text: 'No logs recorded.' }] };
189
+ }
190
+ const text = entries
191
+ .map((e) => {
192
+ const time = new Date(e.timestamp).toLocaleTimeString();
193
+ let s = `[${time}] [${e.level.toUpperCase()}] ${e.message}`;
194
+ if (e.stack)
195
+ s += `\n${e.stack}`;
196
+ return s;
197
+ })
198
+ .join('\n\n');
199
+ return { content: [{ type: 'text', text }] };
200
+ });
201
+ mcp.tool('reload', 'Reload the app bundle on the phone. Triggers both Metro reload broadcast and in-app DevSettings.reload.', {}, async () => {
202
+ await triggerMetroReload();
203
+ try {
204
+ await sendCommand('reload');
205
+ }
206
+ catch { }
207
+ return { content: [{ type: 'text', text: 'App reload triggered successfully.' }] };
208
+ });
209
+ mcp.tool('get_route', 'Get the current active route, pathname, and segments from Expo Router.', {}, async () => {
210
+ const res = await sendCommand('get_route');
211
+ return { content: [{ type: 'text', text: JSON.stringify(res.route, null, 2) }] };
212
+ });
213
+ mcp.tool('get_elements', 'List all currently mounted interactive UI elements (testIDs, titles, types) on screen.', {}, async () => {
214
+ const res = await sendCommand('get_elements');
215
+ const elements = res.elements || [];
216
+ if (elements.length === 0) {
217
+ return { content: [{ type: 'text', text: 'No interactive elements registered on current screen.' }] };
218
+ }
219
+ return { content: [{ type: 'text', text: JSON.stringify(elements, null, 2) }] };
220
+ });
221
+ mcp.tool('get_state', 'Inspect app custom state or stores exposed to the bridge.', {}, async () => {
222
+ const res = await sendCommand('get_state');
223
+ if (res.error)
224
+ throw new Error(res.error);
225
+ return { content: [{ type: 'text', text: JSON.stringify(res.state, null, 2) }] };
226
+ });
227
+ mcp.tool('reset_storage', 'Clear AsyncStorage on the device to test clean first-time install experience.', {}, async () => {
228
+ const res = await sendCommand('reset_storage');
229
+ if (res.error)
230
+ throw new Error(res.error);
231
+ return { content: [{ type: 'text', text: 'AsyncStorage cleared.' }] };
232
+ });
233
+ mcp.tool('open_dev_menu', 'Open the developer menu on the phone without shaking the device.', {}, async () => {
234
+ try {
235
+ await sendCommand('open_dev_menu');
236
+ }
237
+ catch { }
238
+ return { content: [{ type: 'text', text: 'Developer menu triggered.' }] };
239
+ });
240
+ mcp.tool('navigate', 'Navigate to an Expo Router route in the app.', { route: zod_1.z.string().describe('Route path (e.g. "/settings")') }, async ({ route }) => {
241
+ await sendCommand('navigate', { route });
242
+ return { content: [{ type: 'text', text: `Navigated to ${route}` }] };
243
+ });
244
+ mcp.tool('tap', 'Tap an element by its testID prop.', { target: zod_1.z.string().describe('testID of the element') }, async ({ target }) => {
245
+ const res = await sendCommand('tap', { target });
246
+ if (!res.success)
247
+ throw new Error(`Element "${target}" not found or has no onPress.`);
248
+ return { content: [{ type: 'text', text: `Tapped "${target}"` }] };
249
+ });
250
+ mcp.tool('scroll', 'Scroll the active scroll view up or down.', {
251
+ direction: zod_1.z.enum(['up', 'down']),
252
+ amount: zod_1.z.number().optional().describe('Pixels to scroll (default 300)'),
253
+ }, async ({ direction, amount = 300 }) => {
254
+ await sendCommand('scroll', { direction, amount });
255
+ return { content: [{ type: 'text', text: `Scrolled ${direction} ${amount}px` }] };
256
+ });
257
+ mcp.tool('type_text', 'Type text into a TextInput by testID.', {
258
+ target: zod_1.z.string().describe('testID of TextInput'),
259
+ text: zod_1.z.string().describe('Text to type'),
260
+ }, async ({ target, text }) => {
261
+ const res = await sendCommand('type', { target, text });
262
+ if (!res.success)
263
+ throw new Error(`Input "${target}" not found or has no onChangeText handler.`);
264
+ return { content: [{ type: 'text', text: `Typed "${text}" into "${target}"` }] };
265
+ });
266
+ async function run() {
267
+ const transport = new stdio_js_1.StdioServerTransport();
268
+ await mcp.connect(transport);
269
+ process.stderr.write(`[expo-agent-bridge] MCP server running. DevTools plugin: ${PLUGIN_NAME}, Metro port: ${METRO_PORT}\n`);
270
+ }
271
+ run().catch((e) => {
272
+ process.stderr.write(`[expo-agent-bridge] fatal: ${e.message}\n`);
273
+ process.exit(1);
274
+ });
275
+ }
276
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "expo-agent-bridge",
3
+ "version": "0.1.0",
4
+ "description": "Autonomous AI coding agent bridge for Expo and React Native apps. Live visual feedback, logs, navigation, and element interaction without cables.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "expo-agent-bridge": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "skills",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "dev": "tsc --watch",
20
+ "prepare": "npm run build",
21
+ "test": "npm run build && node --test test/**/*.test.js",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "expo",
26
+ "react-native",
27
+ "ai",
28
+ "agent",
29
+ "mcp",
30
+ "devtools",
31
+ "ui-ux",
32
+ "antigravity",
33
+ "cursor",
34
+ "claude-code",
35
+ "windsurf"
36
+ ],
37
+ "author": "Fs02",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/Fs02/expo-agent-bridge"
42
+ },
43
+ "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.30.0",
45
+ "zod": "^3.24.0 || ^4.0.0"
46
+ },
47
+ "peerDependencies": {
48
+ "@expo/devtools": ">=50.0.0",
49
+ "expo": ">=50.0.0",
50
+ "react": ">=18.0.0",
51
+ "react-native": ">=0.73.0",
52
+ "react-native-view-shot": ">=4.0.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "expo-router": {
56
+ "optional": true
57
+ },
58
+ "@react-native-async-storage/async-storage": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^22.0.0",
64
+ "@types/react": "^18.2.0 || ^19.0.0",
65
+ "@types/react-native": "^0.73.0",
66
+ "typescript": "^5.3.0"
67
+ }
68
+ }
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: expo-agent-bridge
3
+ description: >-
4
+ Use when doing UI/UX work on Expo & React Native apps. Teaches how to use the agent
5
+ dev bridge to see live mobile screens, stream console logs & errors, and interact over Wi-Fi or
6
+ WSL+--tunnel — screenshot, logs, reload, navigate, tap, scroll — no cables.
7
+ ---
8
+
9
+ # Expo Agent Dev Bridge
10
+
11
+ Live visual feedback and interactive UI loop for React Native & Expo apps.
12
+ Uses Expo's DevTools Plugin broadcast channel — zero native cables, zero extra tunnels, works over WSL.
13
+
14
+ ## Server Ownership
15
+
16
+ Assume the developer already started Expo/Metro and has the dev app open. Attach to that existing
17
+ server first; do **not** run `expo start`, restart Metro, or create a second dev server unless the
18
+ developer explicitly asks. If the bridge cannot connect, report the connection failure and ask the
19
+ developer to start or expose the server.
20
+
21
+ ## Transport fallback
22
+
23
+ Use the MCP tools when they are available. If an MCP call is unavailable, times
24
+ out, or cannot reach the Expo server, immediately use the equivalent direct CLI
25
+ command below. Do not create a temporary JavaScript client. Both transports use
26
+ the same `expo-agent-bridge` protocol and control the same running app.
27
+
28
+ ## Direct CLI Commands
29
+
30
+ For one-off shell actions, use the installed CLI instead of creating a temporary
31
+ JavaScript client:
32
+
33
+ ```bash
34
+ npx --no-install expo-agent-bridge screenshot /tmp/screen.png
35
+ npx --no-install expo-agent-bridge logs
36
+ npx --no-install expo-agent-bridge navigate /settings
37
+ npx --no-install expo-agent-bridge scroll down 300
38
+ ```
39
+
40
+ If multiple Expo apps are running, each app must use a distinct Metro port. For
41
+ example, initialize this project with `npx --no-install expo-agent-bridge init
42
+ --metro-port 8082` and start Expo with `npx expo start --port 8082`.
43
+
44
+ | CLI command | Description |
45
+ |---|---|
46
+ | `screenshot [file]` | Captures the current mobile screen as a PNG |
47
+ | `logs` | Streams recent errors, warnings, and exceptions |
48
+ | `reload` | Reloads the app bundle on the device |
49
+ | `route` | Returns the current route |
50
+ | `elements` | Lists mounted interactive elements |
51
+ | `state` | Inspects custom app state |
52
+ | `reset-storage` | Clears AsyncStorage |
53
+ | `dev-menu` | Opens the developer menu |
54
+ | `navigate <route>` | Navigates to an Expo Router route |
55
+ | `tap <testID>` | Presses an element by test ID |
56
+ | `scroll <up\|down> [amount]` | Scrolls the active view |
57
+ | `type-text <testID> <text>` | Types into a TextInput |
58
+
59
+ ## Standard Agentic UI/UX Loop
60
+
61
+ 1. `get_screenshot()` — observe current screen
62
+ 2. Edit code
63
+ 3. Wait 3s for Fast Refresh (or call `reload()` if stuck)
64
+ 4. `get_screenshot()` — verify visual changes
65
+ 5. Iterate or commit