@synnode/expo-metro-mcp 1.0.3 → 1.0.6

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.
Files changed (3) hide show
  1. package/README.md +73 -6
  2. package/dist/index.js +40 -16
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -54,27 +54,94 @@ claude mcp add expo-metro --env METRO_PORT=8082 npx @synnode/expo-metro-mcp
54
54
  | `reload` | Reload the React Native app via Metro |
55
55
  | `resolve_stack` | Resolve a stack trace against the Metro source map, showing original file/line instead of bundle offsets |
56
56
  | `list_devices` | List active iOS simulators and Android emulators |
57
- | `screenshot` | Take a screenshot of the active simulator/emulator. Returns the image directly. Optional: `platform`, `device_id` |
57
+ | `screenshot` | Take a screenshot of the active simulator/emulator. Returns the image + pixel dimensions. Optional: `platform`, `device_id` |
58
58
  | `tap` | Tap at x,y coordinates on the active simulator/emulator. Optional: `platform`, `device_id` |
59
59
  | `swipe` | Swipe from one coordinate to another. Optional: `duration_ms`, `platform`, `device_id` |
60
+ | `input_text` | Type text into the focused input field — works without the on-screen keyboard. Optional: `platform`, `device_id` |
61
+ | `input_key` | Send a special key press: `enter`, `backspace`, `delete`, `tab`, `escape`, `back`, `space`, arrow keys. Optional: `platform`, `device_id` |
62
+ | `evaluate` | Run JavaScript inside the connected app runtime via Metro CDP. Supports async expressions. Useful for reading state, calling app helpers, poking navigation, or mutating debug state. Optional: `timeout_ms` |
63
+ | `mmkv_get` | Read a raw string value from a dev-only MMKV debug hook exposed at `globalThis.__EXPO_METRO_MCP__.mmkv` |
64
+ | `mmkv_set` | Write a raw string value through that MMKV debug hook |
65
+ | `mmkv_remove` | Remove a key through that MMKV debug hook |
66
+ | `mmkv_keys` | List all keys available through that MMKV debug hook |
67
+ | `mmkv_get_json` | Read and parse a JSON-valued MMKV entry |
68
+ | `mmkv_set_json` | Store any JSON-serializable value in MMKV without manual stringifying |
69
+ | `mmkv_merge_json` | Shallow-merge an object into an existing JSON-valued MMKV entry |
70
+ | `zustand_persist_get` | Read a persisted Zustand MMKV payload and split out `state` and `version` |
71
+ | `zustand_persist_set` | Write a persisted Zustand payload in `{ state, version? }` shape |
72
+ | `zustand_persist_merge` | Merge fields into an existing persisted Zustand `state` object |
73
+
74
+ ## Runtime evaluation
75
+
76
+ `evaluate` runs JavaScript directly inside the connected React Native app runtime through CDP `Runtime.evaluate`.
77
+
78
+ **What it's good for:**
79
+ - inspect globals, stores, and navigation state
80
+ - call debug helpers or exported functions
81
+ - read or write app persistence through your app's JS runtime
82
+ - toggle feature flags or temporary state during debugging
83
+ - verify assumptions without rebuilding UI automation flows
84
+
85
+ **Notes:**
86
+ - Expressions are awaited automatically, so `Promise` results work out of the box
87
+ - Returned values are serialized when possible; non-serializable objects fall back to their runtime description
88
+ - This is a sharp tool. Great for development, mildly cursed in the wrong hands
89
+
90
+ ## MMKV debug hook
91
+
92
+ If your app uses `react-native-mmkv`, you can expose a tiny dev-only hook and let the MCP seed or inspect persisted state without hand-written eval snippets.
93
+
94
+ Example app-side hook:
95
+
96
+ ```ts
97
+ import {createMMKV} from "react-native-mmkv";
98
+
99
+ export const mmkv = createMMKV({
100
+ id: "synorga-app",
101
+ });
102
+
103
+ if (__DEV__) {
104
+ globalThis.__EXPO_METRO_MCP__ = {
105
+ ...globalThis.__EXPO_METRO_MCP__,
106
+ mmkv: {
107
+ id: "synorga-app",
108
+ getItem: (key: string) => mmkv.getString(key) ?? null,
109
+ setItem: (key: string, value: string) => mmkv.set(key, value),
110
+ removeItem: (key: string) => mmkv.remove(key),
111
+ getAllKeys: () => mmkv.getAllKeys(),
112
+ },
113
+ };
114
+ }
115
+ ```
116
+
117
+ Once exposed, the MCP can use:
118
+ - low-level MMKV tools: `mmkv_get`, `mmkv_set`, `mmkv_remove`, `mmkv_keys`
119
+ - JSON helpers: `mmkv_get_json`, `mmkv_set_json`, `mmkv_merge_json`
120
+ - Zustand helpers: `zustand_persist_get`, `zustand_persist_set`, `zustand_persist_merge`
121
+
122
+ This stays intentionally generic, so it works for persisted Zustand state and plain MMKV usage without coupling the MCP to your store internals.
123
+
124
+ For most AI-driven state seeding, the Zustand helpers are the sweet spot. They avoid hand-building the persisted wrapper shape every time.
60
125
 
61
126
  ## Screenshot & UI automation
62
127
 
63
- `screenshot`, `tap`, and `swipe` interact directly with your running simulator or emulator — no extra packages or paid plans needed.
128
+ `screenshot`, `tap`, `swipe`, `input_text`, and `input_key` interact directly with your running simulator or emulator — no extra packages or paid plans needed.
64
129
 
65
130
  **Requirements:**
66
131
  - **iOS screenshots**: macOS with Xcode installed (`xcrun simctl` must be available)
67
- - **iOS tap/swipe**: `idb` — Facebook's iOS Development Bridge
132
+ - **iOS tap/swipe/input**: `idb` — Facebook's iOS Development Bridge
68
133
  ```bash
69
134
  brew tap facebook/fb && brew install idb-companion
70
135
  pip3 install fb-idb
71
136
  ```
72
- - **Android**: `adb` in your PATH (part of Android SDK platform-tools) — tap, swipe and screenshot all work out of the box
137
+ - **Android**: `adb` in your PATH (part of Android SDK platform-tools) — all tools work out of the box
73
138
 
74
139
  **Notes:**
140
+ - `screenshot` returns the image alongside its pixel dimensions — use those coordinates directly for `tap`/`swipe`, no manual scaling needed
141
+ - `input_text` types into the focused field without requiring the on-screen keyboard to appear
142
+ - To fill a form: `tap` the field → `input_text` the value → `input_key "enter"` to submit
75
143
  - If multiple devices are running, use `list_devices` to find the ID and pass it via `device_id`
76
- - Coordinates are in points (iOS logical pixels) or pixels (Android)
77
- - iOS screenshots work without idb — only tap/swipe require it
144
+ - iOS screenshots work without idb only tap/swipe/input require it
78
145
 
79
146
  ## Using alongside React Native DevTools
80
147
 
package/dist/index.js CHANGED
@@ -1,24 +1,48 @@
1
1
  #!/usr/bin/env node
2
- import{McpServer as Ze}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Ve}from"@modelcontextprotocol/sdk/server/stdio.js";import E from"ws";import me from"http";var fe=parseInt(process.env.METRO_PORT??"8081",10),ge=process.env.METRO_HOST??"localhost",he=parseInt(process.env.LOG_BUFFER_SIZE??"1000",10),ve=3e4,A=1e3,be={log:"log",info:"info",warning:"warn",warn:"warn",error:"error",debug:"debug",dir:"log",dirxml:"log",table:"log",assert:"error"};function ye(e){return e.map(n=>n.value!==void 0&&n.value!==null?String(n.value):n.description?n.description:"").filter(Boolean).join(" ")}async function Se(e,n){return new Promise(t=>{let r=me.get(`http://${e}:${n}/json/list`,i=>{let o="";i.on("data",s=>o+=s),i.on("end",()=>{try{let s=JSON.parse(o);Array.isArray(s)?t(s):t([])}catch{t([])}})});r.on("error",()=>t([])),r.setTimeout(2e3,()=>{r.destroy(),t([])})})}var R=class{buffer=[];cdpWs=null;eventsWs=null;_connected=!1;_currentTargetId=null;_lastConnectedAt=null;_totalReceived=0;_stopped=!1;_eventsBackoff=A;_deviceTitle=null;host=ge;port=fe;get connected(){return this._connected}get lastConnectedAt(){return this._lastConnectedAt}get totalReceived(){return this._totalReceived}get bufferedEntries(){return this.buffer.length}get deviceTitle(){return this._deviceTitle}start(){this._stopped=!1,this.connectEvents()}stop(){this._stopped=!0,this.cdpWs&&(this.cdpWs.terminate(),this.cdpWs=null),this.eventsWs&&(this.eventsWs.terminate(),this.eventsWs=null)}disconnect(){this.cdpWs&&(this.cdpWs.terminate(),this.cdpWs=null),this._connected=!1,this._currentTargetId=null,this._deviceTitle=null}getEntries(n={}){let t=this.buffer;n.since!==void 0&&(t=t.filter(i=>i.timestamp>=n.since)),n.level&&(t=t.filter(i=>i.level===n.level));let r=n.lines??50;return t.slice(-r)}clearBuffer(){let n=this.buffer.length;return this.buffer=[],n}async grabConnection(){return await this.checkForNewTarget(),await new Promise(n=>setTimeout(n,500)),this._connected?`Connected to ${this._deviceTitle??"device"}.`:"No device found. Is Metro running with a connected device?"}addEntry(n){this._totalReceived++,this.buffer.push(n),this.buffer.length>he&&this.buffer.shift()}async checkForNewTarget(){let n=await Se(this.host,this.port);if(!n.length){this._connected&&(this._connected=!1,this._currentTargetId=null,this._deviceTitle=null);return}let t=n[0];t.id===this._currentTargetId&&this.cdpWs?.readyState===E.OPEN||this.connectCdp(t)}connectCdp(n){this.cdpWs&&(this.cdpWs.terminate(),this.cdpWs=null),this._currentTargetId=n.id,this._deviceTitle=n.title??null;let t=new E(n.webSocketDebuggerUrl);this.cdpWs=t,t.on("open",()=>{this._connected=!0,this._lastConnectedAt=new Date,t.send(JSON.stringify({id:1,method:"Runtime.enable",params:{}}))}),t.on("message",r=>{let i;try{i=JSON.parse(r.toString())}catch{return}i.method==="Runtime.consoleAPICalled"&&i.params&&this.handleConsoleEvent(i.params)}),t.on("close",()=>{this.cdpWs===t&&(this._connected=!1,this.cdpWs=null,this._currentTargetId=null,this._deviceTitle=null)}),t.on("error",()=>{})}handleConsoleEvent(n){let t=typeof n.type=="string"?n.type:"log",r=be[t]??"log",i=Array.isArray(n.args)?n.args:[],o=ye(i),s=typeof n.timestamp=="number"?Math.round(n.timestamp):Date.now();if(!o)return;let a=n.stackTrace?.callFrames?.filter(g=>g.url&&!g.url.startsWith("native")).map(g=>({functionName:g.functionName??"(anonymous)",url:g.url,line:g.lineNumber??0,col:g.columnNumber??0})),f=o.includes("http://")?o:void 0;this.addEntry({timestamp:s,level:r,message:o,rawMessage:f,rawFrames:a?.length?a:void 0})}connectEvents(){if(this._stopped)return;let n=`ws://${this.host}:${this.port}/events`,t;try{t=new E(n)}catch{this.scheduleEventsReconnect();return}this.eventsWs=t,t.on("open",()=>{this._eventsBackoff=A}),t.on("message",r=>{let i=null;try{i=JSON.parse(r.toString())}catch{return}(i.type==="build_failed"||i.type==="bundling_error")&&this.addEntry({timestamp:Date.now(),level:"error",message:i.message??i.type})}),t.on("close",()=>{this.eventsWs===t&&(this.eventsWs=null,this.scheduleEventsReconnect())}),t.on("error",()=>{})}scheduleEventsReconnect(){this._stopped||setTimeout(()=>{this._eventsBackoff=Math.min(this._eventsBackoff*2,ve),this.connectEvents()},this._eventsBackoff)}},c=new R;import{z as T}from"zod";var we=/\(https?:\/\/[^)]+\.bundle[^)]*:(\d+:\d+)\)/g;function S(e){return e.replace(we,"(:$1)")}function w(e){let n=new Date(e),t=String(n.getHours()).padStart(2,"0"),r=String(n.getMinutes()).padStart(2,"0"),i=String(n.getSeconds()).padStart(2,"0");return`${t}:${r}:${i}`}var N=T.object({lines:T.coerce.number().int().min(1).max(500).optional().default(50),level:T.enum(["error","warn","info","log","debug"]).optional(),since:T.string().optional()});function xe(e){let n=Number(e);if(!isNaN(n)&&n>1e9)return n<1e12?n*1e3:n;let t=e.match(/^(\d+(?:\.\d+)?)(s|m|h)$/);if(t){let r=parseFloat(t[1]),i=t[2],o={s:1e3,m:6e4,h:36e5};return Date.now()-r*o[i]}return n}function D(e){let n=e.since?xe(e.since):void 0,t=c.getEntries({lines:e.lines,level:e.level,since:n});return t.length===0?"No log entries found.":t.map(r=>`[${w(r.timestamp)}] [${r.level.toUpperCase()}] ${S(r.message)}`).join(`
3
- `)}import{z as L}from"zod";var I=L.object({lines:L.coerce.number().int().min(1).max(200).optional().default(20)});function P(e){let n=c.getEntries({level:"error",lines:e.lines});return n.length===0?"No errors in buffer.":n.map(t=>`[${w(t.timestamp)}] [ERROR]
4
- ${S(t.message)}`).join(`
2
+ import{McpServer as Ot}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Ct}from"@modelcontextprotocol/sdk/server/stdio.js";import $ from"ws";import Be from"http";var Ue=parseInt(process.env.METRO_PORT??"8081",10),Ve=process.env.METRO_HOST??"localhost",Ze=parseInt(process.env.LOG_BUFFER_SIZE??"1000",10),He=3e4,F=1e3,qe={log:"log",info:"info",warning:"warn",warn:"warn",error:"error",debug:"debug",dir:"log",dirxml:"log",table:"log",assert:"error"};function Xe(e){return e.map(t=>t.value!==void 0&&t.value!==null?String(t.value):t.description?t.description:"").filter(Boolean).join(" ")}async function Ye(e,t){return new Promise(n=>{let r=Be.get(`http://${e}:${t}/json/list`,o=>{let i="";o.on("data",a=>i+=a),o.on("end",()=>{try{let a=JSON.parse(i);Array.isArray(a)?n(a):n([])}catch{n([])}})});r.on("error",()=>n([])),r.setTimeout(2e3,()=>{r.destroy(),n([])})})}var A=class{buffer=[];cdpWs=null;eventsWs=null;_connected=!1;_currentTargetId=null;_lastConnectedAt=null;_totalReceived=0;_stopped=!1;_eventsBackoff=F;_deviceTitle=null;_nextMessageId=1;pendingResponses=new Map;host=Ve;port=Ue;get connected(){return this._connected}get lastConnectedAt(){return this._lastConnectedAt}get totalReceived(){return this._totalReceived}get bufferedEntries(){return this.buffer.length}get deviceTitle(){return this._deviceTitle}start(){this._stopped=!1,this.connectEvents()}stop(){this._stopped=!0,this.cdpWs&&(this.cdpWs.terminate(),this.cdpWs=null),this.eventsWs&&(this.eventsWs.terminate(),this.eventsWs=null)}disconnect(){this.rejectPendingResponses(new Error("Disconnected from Metro CDP.")),this.cdpWs&&(this.cdpWs.terminate(),this.cdpWs=null),this._connected=!1,this._currentTargetId=null,this._deviceTitle=null}getEntries(t={}){let n=this.buffer;t.since!==void 0&&(n=n.filter(o=>o.timestamp>=t.since)),t.level&&(n=n.filter(o=>o.level===t.level));let r=t.lines??50;return n.slice(-r)}clearBuffer(){let t=this.buffer.length;return this.buffer=[],t}async grabConnection(){return await this.checkForNewTarget(),await new Promise(t=>setTimeout(t,500)),this._connected?`Connected to ${this._deviceTitle??"device"}.`:"No device found. Is Metro running with a connected device?"}async evaluate(t,n=5e3){if((!this._connected||!this.cdpWs||this.cdpWs.readyState!==$.OPEN)&&(await this.checkForNewTarget(),await new Promise(r=>setTimeout(r,250))),!this._connected||!this.cdpWs||this.cdpWs.readyState!==$.OPEN)throw new Error("Not connected to Metro CDP. Start Expo, make sure a device is attached, then call connect.");return this.sendCdpCommand("Runtime.evaluate",{expression:t,awaitPromise:!0,returnByValue:!0,generatePreview:!1,replMode:!0},n)}addEntry(t){this._totalReceived++,this.buffer.push(t),this.buffer.length>Ze&&this.buffer.shift()}async checkForNewTarget(){let t=await Ye(this.host,this.port);if(!t.length){this._connected&&(this._connected=!1,this._currentTargetId=null,this._deviceTitle=null);return}let n=t[0];n.id===this._currentTargetId&&this.cdpWs?.readyState===$.OPEN||this.connectCdp(n)}connectCdp(t){this.cdpWs&&(this.cdpWs.terminate(),this.cdpWs=null),this._currentTargetId=t.id,this._deviceTitle=t.title??null;let n=new $(t.webSocketDebuggerUrl);this.cdpWs=n,n.on("open",()=>{this._connected=!0,this._lastConnectedAt=new Date,n.send(JSON.stringify({id:1,method:"Runtime.enable",params:{}}))}),n.on("message",r=>{let o;try{o=JSON.parse(r.toString())}catch{return}if(typeof o.id=="number"){let i=this.pendingResponses.get(o.id);if(i){clearTimeout(i.timer),this.pendingResponses.delete(o.id),i.resolve(o);return}}o.method==="Runtime.consoleAPICalled"&&o.params&&this.handleConsoleEvent(o.params)}),n.on("close",()=>{this.cdpWs===n&&(this.rejectPendingResponses(new Error("Metro CDP connection closed.")),this._connected=!1,this.cdpWs=null,this._currentTargetId=null,this._deviceTitle=null)}),n.on("error",()=>{})}sendCdpCommand(t,n,r){let o=this.cdpWs;if(!o||o.readyState!==$.OPEN)return Promise.reject(new Error("Metro CDP is not connected."));let i=this._nextMessageId++;return new Promise((a,u)=>{let l=setTimeout(()=>{this.pendingResponses.delete(i),u(new Error(`CDP command timed out after ${r}ms.`))},r);this.pendingResponses.set(i,{resolve:a,reject:u,timer:l}),o.send(JSON.stringify({id:i,method:t,params:n}),p=>{p&&(clearTimeout(l),this.pendingResponses.delete(i),u(p instanceof Error?p:new Error(String(p))))})})}rejectPendingResponses(t){for(let[n,r]of this.pendingResponses.entries())clearTimeout(r.timer),r.reject(t),this.pendingResponses.delete(n)}handleConsoleEvent(t){let n=typeof t.type=="string"?t.type:"log",r=qe[n]??"log",o=Array.isArray(t.args)?t.args:[],i=Xe(o),a=typeof t.timestamp=="number"?Math.round(t.timestamp):Date.now();if(!i)return;let l=t.stackTrace?.callFrames?.filter(g=>g.url&&!g.url.startsWith("native")).map(g=>({functionName:g.functionName??"(anonymous)",url:g.url,line:g.lineNumber??0,col:g.columnNumber??0})),p=i.includes("http://")?i:void 0;this.addEntry({timestamp:a,level:r,message:i,rawMessage:p,rawFrames:l?.length?l:void 0})}connectEvents(){if(this._stopped)return;let t=`ws://${this.host}:${this.port}/events`,n;try{n=new $(t)}catch{this.scheduleEventsReconnect();return}this.eventsWs=n,n.on("open",()=>{this._eventsBackoff=F}),n.on("message",r=>{let o=null;try{o=JSON.parse(r.toString())}catch{return}(o.type==="build_failed"||o.type==="bundling_error")&&this.addEntry({timestamp:Date.now(),level:"error",message:o.message??o.type})}),n.on("close",()=>{this.eventsWs===n&&(this.eventsWs=null,this.scheduleEventsReconnect())}),n.on("error",()=>{})}scheduleEventsReconnect(){this._stopped||setTimeout(()=>{this._eventsBackoff=Math.min(this._eventsBackoff*2,He),this.connectEvents()},this._eventsBackoff)}},c=new A;import{z as N}from"zod";var Qe=/\(https?:\/\/[^)]+\.bundle[^)]*:(\d+:\d+)\)/g;function M(e){return e.replace(Qe,"(:$1)")}function T(e){let t=new Date(e),n=String(t.getHours()).padStart(2,"0"),r=String(t.getMinutes()).padStart(2,"0"),o=String(t.getSeconds()).padStart(2,"0");return`${n}:${r}:${o}`}var K=N.object({lines:N.coerce.number().int().min(1).max(500).optional().default(50),level:N.enum(["error","warn","info","log","debug"]).optional(),since:N.string().optional()});function et(e){let t=Number(e);if(!isNaN(t)&&t>1e9)return t<1e12?t*1e3:t;let n=e.match(/^(\d+(?:\.\d+)?)(s|m|h)$/);if(n){let r=parseFloat(n[1]),o=n[2],i={s:1e3,m:6e4,h:36e5};return Date.now()-r*i[o]}return t}function G(e){let t=e.since?et(e.since):void 0,n=c.getEntries({lines:e.lines,level:e.level,since:t});return n.length===0?"No log entries found.":n.map(r=>`[${T(r.timestamp)}] [${r.level.toUpperCase()}] ${M(r.message)}`).join(`
3
+ `)}import{z as B}from"zod";var U=B.object({lines:B.coerce.number().int().min(1).max(200).optional().default(20)});function V(e){let t=c.getEntries({level:"error",lines:e.lines});return t.length===0?"No errors in buffer.":t.map(n=>`[${T(n.timestamp)}] [ERROR]
4
+ ${M(n.message)}`).join(`
5
5
 
6
6
  ---
7
7
 
8
- `)}function F(){let e={connected:c.connected,host:c.host,port:c.port,device:c.deviceTitle,buffered_entries:c.bufferedEntries,last_connected_at:c.lastConnectedAt?.toISOString()??null,total_received:c.totalReceived,expo_sdk_version:null};return JSON.stringify(e,null,2)}function W(){let e=c.clearBuffer();return`Cleared ${e} log ${e===1?"entry":"entries"} from the buffer.`}import{z as M}from"zod";var B=M.object({duration:M.string().optional().default("10s"),level:M.enum(["error","warn","info","log","debug"]).optional()});function $e(e){let n=e.match(/^(\d+(?:\.\d+)?)(s|m)$/);if(!n)return 1e4;let t=parseFloat(n[1]),i=n[2]==="m"?t*6e4:t*1e3;return Math.min(i,3e4)}var _e=500;async function j(e){if(!c.connected)return"Metro is not connected. Start Expo dev server and try again.";let n=$e(e.duration),t=c.bufferedEntries,r=Date.now(),i=e.level;await new Promise(a=>{let f=setInterval(()=>{Date.now()-r>=n&&(clearInterval(f),a())},_e)});let s=c.getEntries({lines:c.bufferedEntries}).slice(t),l=i?s.filter(a=>a.level===i):s;return l.length===0?`No logs received during ${e.duration} window.`:l.map(a=>`[${w(a.timestamp)}] [${a.level.toUpperCase()}] ${S(a.message)}`).join(`
9
- `)}import Te from"http";var ke=parseInt(process.env.METRO_PORT??"8081",10),Ee=process.env.METRO_HOST??"localhost";async function U(){return new Promise(e=>{let n=Te.request({hostname:Ee,port:ke,path:"/reload",method:"POST"},t=>{t.resume(),t.statusCode===200?e("App reloaded."):e(`Reload failed: HTTP ${t.statusCode}`)});n.on("error",t=>e(`Reload failed: ${t.message}`)),n.setTimeout(3e3,()=>{n.destroy(),e("Reload failed: timeout")}),n.end()})}import{z}from"zod";import Re from"http";import{SourceMapConsumer as Me}from"source-map";var G=z.object({message:z.string().optional()}),k=new Map,Oe=6e4;function H(e,n,t){return e.replace(/^https?:\/\/[^/]+/,`http://${n}:${t}`)}function K(e){let n=e.match(/(?:\/\/&|\?)(.+)$/),t=n?n[1]:"dev=true&minify=false";return`${e.replace(/(\/[^/?]+)\.bundle.*$/,"$1.map")}?${t}`}async function Ce(e){let n=k.get(e);return n&&Date.now()-n.fetchedAt<Oe?n.consumer:new Promise(t=>{let r=new URL(e),i=Re.get({hostname:r.hostname,port:Number(r.port)||8081,path:r.pathname+r.search},o=>{let s=[];o.on("data",l=>s.push(l)),o.on("end",async()=>{if(o.statusCode!==200){t(null);return}try{let l=JSON.parse(Buffer.concat(s).toString()),a=await Me.with(l,null,f=>f);k.set(e,{consumer:a,fetchedAt:Date.now()}),t(a)}catch{t(null)}})});i.on("error",()=>t(null)),i.setTimeout(1e4,()=>{i.destroy(),t(null)})})}function q(){k.forEach(e=>e.consumer.destroy()),k.clear()}function Ae(e){let n=/at\s+([\w$.<>[\] ]+?)\s+\((https?:\/\/[^)]+\.bundle[^)]*):(\d+):(\d+)\)/g,t=[],r;for(;(r=n.exec(e))!==null;)t.push({functionName:r[1].trim(),url:r[2],line:parseInt(r[3])-1,col:parseInt(r[4])});return t}async function Ne(e,n){let t=[],r=new Map;for(let o of e){let s=H(o.url,c.host,c.port),l=K(s);r.has(l)||r.set(l,[]),r.get(l).push(o)}let i=new Map;await Promise.all([...r.keys()].map(async o=>{i.set(o,await Ce(o))}));for(let o of e){let s=H(o.url,c.host,c.port),l=K(s),a=i.get(l)??null;if(a){let f=a.originalPositionFor({line:o.line+1,column:o.col});if(f.source){let g=f.source.replace(/^.*\/\/\//,"").replace(/\?.*$/,"");t.push(` at ${o.functionName} (${g}:${f.line}:${f.column})`);continue}}t.push(` at ${o.functionName} (:${o.line+1}:${o.col})`)}return t}async function J(e){let n=c.getEntries({level:"error",lines:50}),t=e.message?[...n].reverse().find(a=>a.message.includes(e.message)):n.at(-1);if(!t)return"No error entries in buffer.";let r=`[ERROR] ${t.message.split(`
10
- `)[0]}`;if(!t.rawMessage)return`${r}
8
+ `)}function Z(){let e={connected:c.connected,host:c.host,port:c.port,device:c.deviceTitle,buffered_entries:c.bufferedEntries,last_connected_at:c.lastConnectedAt?.toISOString()??null,total_received:c.totalReceived,expo_sdk_version:null};return JSON.stringify(e,null,2)}function H(){let e=c.clearBuffer();return`Cleared ${e} log ${e===1?"entry":"entries"} from the buffer.`}import{z as j}from"zod";var q=j.object({duration:j.string().optional().default("10s"),level:j.enum(["error","warn","info","log","debug"]).optional()});function tt(e){let t=e.match(/^(\d+(?:\.\d+)?)(s|m)$/);if(!t)return 1e4;let n=parseFloat(t[1]),o=t[2]==="m"?n*6e4:n*1e3;return Math.min(o,3e4)}var nt=500;async function X(e){if(!c.connected)return"Metro is not connected. Start Expo dev server and try again.";let t=tt(e.duration),n=c.bufferedEntries,r=Date.now(),o=e.level;await new Promise(l=>{let p=setInterval(()=>{Date.now()-r>=t&&(clearInterval(p),l())},nt)});let a=c.getEntries({lines:c.bufferedEntries}).slice(n),u=o?a.filter(l=>l.level===o):a;return u.length===0?`No logs received during ${e.duration} window.`:u.map(l=>`[${T(l.timestamp)}] [${l.level.toUpperCase()}] ${M(l.message)}`).join(`
9
+ `)}import rt from"http";var ot=parseInt(process.env.METRO_PORT??"8081",10),it=process.env.METRO_HOST??"localhost";async function Y(){return new Promise(e=>{let t=rt.request({hostname:it,port:ot,path:"/reload",method:"POST"},n=>{n.resume(),n.statusCode===200?e("App reloaded."):e(`Reload failed: HTTP ${n.statusCode}`)});t.on("error",n=>e(`Reload failed: ${n.message}`)),t.setTimeout(3e3,()=>{t.destroy(),e("Reload failed: timeout")}),t.end()})}import{z as Q}from"zod";import st from"http";import{SourceMapConsumer as at}from"source-map";var ne=Q.object({message:Q.string().optional()}),D=new Map,ct=6e4;function ee(e,t,n){return e.replace(/^https?:\/\/[^/]+/,`http://${t}:${n}`)}function te(e){let t=e.match(/(?:\/\/&|\?)(.+)$/),n=t?t[1]:"dev=true&minify=false";return`${e.replace(/(\/[^/?]+)\.bundle.*$/,"$1.map")}?${n}`}async function ut(e){let t=D.get(e);return t&&Date.now()-t.fetchedAt<ct?t.consumer:new Promise(n=>{let r=new URL(e),o=st.get({hostname:r.hostname,port:Number(r.port)||8081,path:r.pathname+r.search},i=>{let a=[];i.on("data",u=>a.push(u)),i.on("end",async()=>{if(i.statusCode!==200){n(null);return}try{let u=JSON.parse(Buffer.concat(a).toString()),l=await at.with(u,null,p=>p);D.set(e,{consumer:l,fetchedAt:Date.now()}),n(l)}catch{n(null)}})});o.on("error",()=>n(null)),o.setTimeout(1e4,()=>{o.destroy(),n(null)})})}function re(){D.forEach(e=>e.consumer.destroy()),D.clear()}function lt(e){let t=/at\s+([\w$.<>[\] ]+?)\s+\((https?:\/\/[^)]+\.bundle[^)]*):(\d+):(\d+)\)/g,n=[],r;for(;(r=t.exec(e))!==null;)n.push({functionName:r[1].trim(),url:r[2],line:parseInt(r[3])-1,col:parseInt(r[4])});return n}async function mt(e,t){let n=[],r=new Map;for(let i of e){let a=ee(i.url,c.host,c.port),u=te(a);r.has(u)||r.set(u,[]),r.get(u).push(i)}let o=new Map;await Promise.all([...r.keys()].map(async i=>{o.set(i,await ut(i))}));for(let i of e){let a=ee(i.url,c.host,c.port),u=te(a),l=o.get(u)??null;if(l){let p=l.originalPositionFor({line:i.line+1,column:i.col});if(p.source){let g=p.source.replace(/^.*\/\/\//,"").replace(/\?.*$/,"");n.push(` at ${i.functionName} (${g}:${p.line}:${p.column})`);continue}}n.push(` at ${i.functionName} (:${i.line+1}:${i.col})`)}return n}async function oe(e){let t=c.getEntries({level:"error",lines:50}),n=e.message?[...t].reverse().find(l=>l.message.includes(e.message)):t.at(-1);if(!n)return"No error entries in buffer.";let r=`[ERROR] ${n.message.split(`
10
+ `)[0]}`;if(!n.rawMessage)return`${r}
11
11
 
12
- No stack frames available.`;let i=Ae(t.rawMessage);if(!i.length)return`${r}
12
+ No stack frames available.`;let o=lt(n.rawMessage);if(!o.length)return`${r}
13
13
 
14
- No stack frames available.`;let o=await Ne(i),s=o.filter(a=>!a.includes("node_modules")&&!a.includes("(:")),l=[r,""];return s.length?l.push(...s):l.push(...o),l.join(`
15
- `)}import{z as O}from"zod";import{execSync as b}from"child_process";import*as y from"fs";import*as V from"os";import*as Q from"path";import{execSync as Y}from"child_process";function X(e){try{return Y(e,{timeout:5e3,stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return""}}function Z(e){try{return Y(`which ${e}`,{timeout:2e3,stdio:"ignore"}),!0}catch{return!1}}function De(){if(!Z("xcrun"))return[];let e=X("xcrun simctl list devices booted --json");if(!e)return[];try{let n=JSON.parse(e),t=[];for(let[,r]of Object.entries(n.devices))for(let i of r)i.state==="Booted"&&t.push({id:i.udid,name:i.name,platform:"ios"});return t}catch{return[]}}function Le(){if(!Z("adb"))return[];let e=X("adb devices -l");if(!e)return[];let n=[],t=e.split(`
16
- `).slice(1);for(let r of t){let i=r.trim().split(/\s+/);if(i.length<2||i[1]!=="device")continue;let o=i[0],s=r.match(/model:(\S+)/),l=s?s[1].replace(/_/g," "):o;n.push({id:o,name:l,platform:"android"})}return n}function d(){return[...De(),...Le()]}function h(e,n){let t=d();return t.length?n?t.find(r=>r.id===n||r.name.toLowerCase().includes(n.toLowerCase()))??null:e==="ios"?t.find(r=>r.platform==="ios")??null:e==="android"?t.find(r=>r.platform==="android")??null:t.find(r=>r.platform==="ios")??t[0]:null}var ee=O.object({device_id:O.string().optional(),platform:O.enum(["ios","android"]).optional()});function Ie(e){try{let n=b(`sips -g pixelWidth -g pixelHeight "${e}"`,{timeout:5e3,stdio:["ignore","pipe","ignore"]}).toString(),t=n.match(/pixelWidth:\s*(\d+)/),r=n.match(/pixelHeight:\s*(\d+)/);if(!t||!r)return 1;let i=parseInt(t[1]),o=parseInt(r[1]),s=Math.max(i,o);return s>=2500&&i%3===0||s>=2500?3:s>=1334?2:1}catch{return 1}}function Pe(e,n){b(`xcrun simctl io "${e}" screenshot "${n}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]});let t=Ie(n);if(t>1)try{let r=b(`sips -g pixelWidth -g pixelHeight "${n}"`,{timeout:5e3,stdio:["ignore","pipe","ignore"]}).toString(),i=r.match(/pixelWidth:\s*(\d+)/),o=r.match(/pixelHeight:\s*(\d+)/);if(i&&o){let s=Math.round(parseInt(i[1])/t),l=Math.round(parseInt(o[1])/t),a=n.replace(".png","-points.png");b(`sips -z ${l} ${s} "${n}" --out "${a}"`,{timeout:1e4,stdio:"ignore"}),y.renameSync(a,n)}}catch{}}function Fe(e,n){let t=`/sdcard/mcp_screenshot_${Date.now()}.png`;b(`adb -s "${e}" shell screencap -p "${t}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]}),b(`adb -s "${e}" pull "${t}" "${n}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]}),b(`adb -s "${e}" shell rm "${t}"`,{timeout:5e3,stdio:"ignore"})}function We(e){if(e.length<24||e.readUInt32BE(0)!==2303741511)return null;let n=e.readUInt32BE(16),t=e.readUInt32BE(20);return{width:n,height:t}}function te(e){let n=d();if(!n.length)return{type:"text",text:"No active simulators or emulators found. Start a simulator (iOS) or emulator (Android) first."};let t=h(e.platform,e.device_id);if(!t)return{type:"text",text:`No matching device found. Available: ${n.map(i=>`${i.name} (${i.platform})`).join(", ")}`};let r=Q.join(V.tmpdir(),`expo-mcp-screenshot-${Date.now()}.png`);try{t.platform==="ios"?Pe(t.id,r):Fe(t.id,r);let i=y.readFileSync(r),o=i.toString("base64"),s=We(i);return y.unlinkSync(r),{type:"image",data:o,mimeType:"image/png",width:s?.width??0,height:s?.height??0}}catch(i){try{y.unlinkSync(r)}catch{}return{type:"text",text:`Screenshot failed: ${i instanceof Error?i.message:String(i)}`}}}import{z as p}from"zod";import{execSync as $}from"child_process";import{spawn as Be}from"child_process";var m=null,C=null;function x(e){m&&!m.killed&&C===e||(m&&!m.killed&&m.kill(),m=Be("idb_companion",["--udid",e],{detached:!1,stdio:"ignore"}),C=e,m.on("exit",()=>{m=null,C=null}),Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,800))}process.on("exit",()=>m?.kill());process.on("SIGTERM",()=>{m?.kill(),process.exit(0)});process.on("SIGINT",()=>{m?.kill(),process.exit(0)});var ne=p.object({x:p.number().int().describe("X coordinate in points/pixels"),y:p.number().int().describe("Y coordinate in points/pixels"),device_id:p.string().optional(),platform:p.enum(["ios","android"]).optional()}),re=p.object({x1:p.number().int(),y1:p.number().int(),x2:p.number().int(),y2:p.number().int(),duration_ms:p.number().int().min(50).max(5e3).optional().default(300),device_id:p.string().optional(),platform:p.enum(["ios","android"]).optional()});function ie(){try{return $("which idb",{timeout:2e3,stdio:"ignore"}),!0}catch{return!1}}function je(e,n,t){if(ie()){x(e),$(`idb ui tap ${n} ${t} --udid "${e}"`,{timeout:5e3,stdio:["ignore","ignore","pipe"]});return}let r=JSON.stringify({touches:[{x:n,y:t,action:"began"}]});try{$(`echo '${r}' | xcrun simctl io "${e}" sendtouchJSON -`,{timeout:5e3,stdio:["pipe","ignore","pipe"]});return}catch{}throw new Error(`iOS tap requires idb (Facebook iOS Development Bridge).
14
+ No stack frames available.`;let i=await mt(o),a=i.filter(l=>!l.includes("node_modules")&&!l.includes("(:")),u=[r,""];return a.length?u.push(...a):u.push(...i),u.join(`
15
+ `)}import{z as L}from"zod";import{execSync as w}from"child_process";import*as k from"fs";import*as ce from"os";import*as ue from"path";import{execSync as ie}from"child_process";function se(e){try{return ie(e,{timeout:5e3,stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return""}}function ae(e){try{return ie(`which ${e}`,{timeout:2e3,stdio:"ignore"}),!0}catch{return!1}}function pt(){if(!ae("xcrun"))return[];let e=se("xcrun simctl list devices booted --json");if(!e)return[];try{let t=JSON.parse(e),n=[];for(let[,r]of Object.entries(t.devices))for(let o of r)o.state==="Booted"&&n.push({id:o.udid,name:o.name,platform:"ios"});return n}catch{return[]}}function dt(){if(!ae("adb"))return[];let e=se("adb devices -l");if(!e)return[];let t=[],n=e.split(`
16
+ `).slice(1);for(let r of n){let o=r.trim().split(/\s+/);if(o.length<2||o[1]!=="device")continue;let i=o[0],a=r.match(/model:(\S+)/),u=a?a[1].replace(/_/g," "):i;t.push({id:i,name:u,platform:"android"})}return t}function h(){return[...pt(),...dt()]}function x(e,t){let n=h();return n.length?t?n.find(r=>r.id===t||r.name.toLowerCase().includes(t.toLowerCase()))??null:e==="ios"?n.find(r=>r.platform==="ios")??null:e==="android"?n.find(r=>r.platform==="android")??null:n.find(r=>r.platform==="ios")??n[0]:null}var le=L.object({device_id:L.string().optional(),platform:L.enum(["ios","android"]).optional()});function ft(e){try{let t=w(`sips -g pixelWidth -g pixelHeight "${e}"`,{timeout:5e3,stdio:["ignore","pipe","ignore"]}).toString(),n=t.match(/pixelWidth:\s*(\d+)/),r=t.match(/pixelHeight:\s*(\d+)/);if(!n||!r)return 1;let o=parseInt(n[1]),i=parseInt(r[1]),a=Math.max(o,i);return a>=2500&&o%3===0||a>=2500?3:a>=1334?2:1}catch{return 1}}function gt(e,t){w(`xcrun simctl io "${e}" screenshot "${t}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]});let n=ft(t);if(n>1)try{let r=w(`sips -g pixelWidth -g pixelHeight "${t}"`,{timeout:5e3,stdio:["ignore","pipe","ignore"]}).toString(),o=r.match(/pixelWidth:\s*(\d+)/),i=r.match(/pixelHeight:\s*(\d+)/);if(o&&i){let a=Math.round(parseInt(o[1])/n),u=Math.round(parseInt(i[1])/n),l=t.replace(".png","-points.png");w(`sips -z ${u} ${a} "${t}" --out "${l}"`,{timeout:1e4,stdio:"ignore"}),k.renameSync(l,t)}}catch{}}function ht(e,t){let n=`/sdcard/mcp_screenshot_${Date.now()}.png`;w(`adb -s "${e}" shell screencap -p "${n}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]}),w(`adb -s "${e}" pull "${n}" "${t}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]}),w(`adb -s "${e}" shell rm "${n}"`,{timeout:5e3,stdio:"ignore"})}function vt(e){if(e.length<24||e.readUInt32BE(0)!==2303741511)return null;let t=e.readUInt32BE(16),n=e.readUInt32BE(20);return{width:t,height:n}}function me(e){let t=h();if(!t.length)return{type:"text",text:"No active simulators or emulators found. Start a simulator (iOS) or emulator (Android) first."};let n=x(e.platform,e.device_id);if(!n)return{type:"text",text:`No matching device found. Available: ${t.map(o=>`${o.name} (${o.platform})`).join(", ")}`};let r=ue.join(ce.tmpdir(),`expo-mcp-screenshot-${Date.now()}.png`);try{n.platform==="ios"?gt(n.id,r):ht(n.id,r);let o=k.readFileSync(r),i=o.toString("base64"),a=vt(o);return k.unlinkSync(r),{type:"image",data:i,mimeType:"image/png",width:a?.width??0,height:a?.height??0}}catch(o){try{k.unlinkSync(r)}catch{}return{type:"text",text:`Screenshot failed: ${o instanceof Error?o.message:String(o)}`}}}import{z as f}from"zod";import{execSync as P}from"child_process";import{spawn as yt}from"child_process";var v=null,z=null;function R(e){v&&!v.killed&&z===e||(v&&!v.killed&&v.kill(),v=yt("idb_companion",["--udid",e],{detached:!1,stdio:"ignore"}),z=e,v.on("exit",()=>{v=null,z=null}),Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,800))}process.on("exit",()=>v?.kill());process.on("SIGTERM",()=>{v?.kill(),process.exit(0)});process.on("SIGINT",()=>{v?.kill(),process.exit(0)});var pe=f.object({x:f.number().int().describe("X coordinate in points/pixels"),y:f.number().int().describe("Y coordinate in points/pixels"),device_id:f.string().optional(),platform:f.enum(["ios","android"]).optional()}),de=f.object({x1:f.number().int(),y1:f.number().int(),x2:f.number().int(),y2:f.number().int(),duration_ms:f.number().int().min(50).max(5e3).optional().default(300),device_id:f.string().optional(),platform:f.enum(["ios","android"]).optional()});function fe(){try{return P("which idb",{timeout:2e3,stdio:"ignore"}),!0}catch{return!1}}function bt(e,t,n){if(fe()){R(e),P(`idb ui tap ${t} ${n} --udid "${e}"`,{timeout:5e3,stdio:["ignore","ignore","pipe"]});return}let r=JSON.stringify({touches:[{x:t,y:n,action:"began"}]});try{P(`echo '${r}' | xcrun simctl io "${e}" sendtouchJSON -`,{timeout:5e3,stdio:["pipe","ignore","pipe"]});return}catch{}throw new Error(`iOS tap requires idb (Facebook iOS Development Bridge).
17
17
  Install via: brew install idb-companion && pip3 install fb-idb
18
- Or: brew install facebook/fb/idb-companion`)}function Ue(e,n,t){$(`adb -s "${e}" shell input tap ${n} ${t}`,{timeout:5e3,stdio:["ignore","ignore","pipe"]})}function ze(e,n,t,r,i,o){if(ie()){x(e);let s=(o/1e3).toFixed(2);$(`idb ui swipe ${n} ${t} ${r} ${i} ${s} --udid "${e}"`,{timeout:o+5e3,stdio:["ignore","ignore","pipe"]});return}throw new Error(`iOS swipe requires idb (Facebook iOS Development Bridge).
19
- Install via: brew install idb-companion && pip3 install fb-idb`)}function He(e,n,t,r,i,o){$(`adb -s "${e}" shell input swipe ${n} ${t} ${r} ${i} ${o}`,{timeout:1e4,stdio:["ignore","ignore","pipe"]})}function oe(e){let n=d();if(!n.length)return"No active simulators or emulators found.";let t=h(e.platform,e.device_id);if(!t)return`No matching device found. Available: ${n.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return t.platform==="ios"?je(t.id,e.x,e.y):Ue(t.id,e.x,e.y),`Tapped (${e.x}, ${e.y}) on ${t.name} [${t.platform}].`}catch(r){return`Tap failed: ${r instanceof Error?r.message:String(r)}`}}function se(e){let n=d();if(!n.length)return"No active simulators or emulators found.";let t=h(e.platform,e.device_id);if(!t)return`No matching device found. Available: ${n.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return t.platform==="ios"?(ze(t.id,e.x1,e.y1,e.x2,e.y2,e.duration_ms),`Swiped from (${e.x1}, ${e.y1}) to (${e.x2}, ${e.y2}) on ${t.name} [ios]. Note: iOS swipe simulation is limited \u2014 use Android for reliable swipe gestures.`):(He(t.id,e.x1,e.y1,e.x2,e.y2,e.duration_ms),`Swiped from (${e.x1}, ${e.y1}) to (${e.x2}, ${e.y2}) on ${t.name} [android] over ${e.duration_ms}ms.`)}catch(r){return`Swipe failed: ${r instanceof Error?r.message:String(r)}`}}import{z as v}from"zod";import{execSync as _}from"child_process";var ce=v.object({text:v.string().describe("Text to type into the focused input field"),device_id:v.string().optional(),platform:v.enum(["ios","android"]).optional()}),ae=v.object({key:v.enum(["enter","backspace","delete","tab","escape","home","end","back","space","up","down","left","right"]).describe("Key to press"),device_id:v.string().optional(),platform:v.enum(["ios","android"]).optional()}),Ke={enter:66,backspace:67,delete:67,tab:61,escape:111,home:3,end:123,back:4,space:62,up:19,down:20,left:21,right:22},Ge={enter:40,backspace:42,delete:76,tab:43,escape:41,home:74,end:77,back:41,space:44,up:82,down:81,left:80,right:79};function le(){try{return _("which idb",{timeout:2e3,stdio:"ignore"}),!0}catch{return!1}}function qe(e,n){if(le()){x(e);let t=n.replace(/'/g,`'"'"'`);_(`idb ui text '${t}' --udid "${e}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]});return}throw new Error(`iOS text input requires idb.
20
- Install via: brew install idb-companion && pip3 install fb-idb`)}function Je(e,n){let t=n.replace(/\\/g,"\\\\").replace(/ /g,"%s").replace(/'/g,"\\'").replace(/"/g,'\\"');_(`adb -s "${e}" shell input text "${t}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]})}function Ye(e,n){if(le()){x(e);let t=Ge[n];if(t===void 0)throw new Error(`Unknown key: ${n}`);_(`idb ui key ${t} --udid "${e}"`,{timeout:5e3,stdio:["ignore","ignore","pipe"]});return}throw new Error(`iOS key input requires idb.
21
- Install via: brew install idb-companion && pip3 install fb-idb`)}function Xe(e,n){let t=Ke[n];if(t===void 0)throw new Error(`Unknown key: ${n}`);_(`adb -s "${e}" shell input keyevent ${t}`,{timeout:5e3,stdio:["ignore","ignore","pipe"]})}function ue(e){let n=d();if(!n.length)return"No active simulators or emulators found.";let t=h(e.platform,e.device_id);if(!t)return`No matching device found. Available: ${n.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return t.platform==="ios"?qe(t.id,e.text):Je(t.id,e.text),`Typed "${e.text}" on ${t.name} [${t.platform}].`}catch(r){return`input_text failed: ${r instanceof Error?r.message:String(r)}`}}function pe(e){let n=d();if(!n.length)return"No active simulators or emulators found.";let t=h(e.platform,e.device_id);if(!t)return`No matching device found. Available: ${n.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return t.platform==="ios"?Ye(t.id,e.key):Xe(t.id,e.key),`Key "${e.key}" sent to ${t.name} [${t.platform}].`}catch(r){return`input_key failed: ${r instanceof Error?r.message:String(r)}`}}function de(){let e=d();if(!e.length)return"No active simulators or emulators found.\n\n- iOS: start a simulator via Xcode or `xcrun simctl boot <device>`\n- Android: start an emulator via Android Studio or `emulator -avd <name>`";let n=e.map(t=>`- ${t.name} [${t.platform}] id: ${t.id}`);return`Active devices (${e.length}):
22
- ${n.join(`
23
- `)}`}var u=new Ze({name:"expo-metro-mcp",version:"0.1.0"});u.registerTool("get_logs",{description:"Fetch recent logs from the Metro dev server buffer. Supports filtering by level and time.",inputSchema:N.shape},async e=>({content:[{type:"text",text:D(e)}]}));u.registerTool("get_errors",{description:"Fetch recent errors from the Metro dev server buffer, with stack traces.",inputSchema:I.shape},async e=>({content:[{type:"text",text:P(e)}]}));u.registerTool("get_status",{description:"Check the connection status of the Metro dev server and buffer statistics."},async()=>({content:[{type:"text",text:F()}]}));u.registerTool("connect",{description:"Grab the CDP connection to the Metro dev server. Use this if get_status shows disconnected, or to take over the connection from React Native DevTools."},async()=>({content:[{type:"text",text:await c.grabConnection()}]}));u.registerTool("disconnect",{description:"Release the CDP connection so React Native DevTools can connect. Use this before switching to DevTools. Call connect when you want to reattach."},async()=>(c.disconnect(),{content:[{type:"text",text:"Disconnected. DevTools can now connect freely."}]}));u.registerTool("clear_logs",{description:"Clear the internal log buffer. Useful after resolving an issue."},async()=>({content:[{type:"text",text:W()}]}));u.registerTool("watch_logs",{description:"Listen for incoming logs for a short time window and return all collected entries.",inputSchema:B.shape},async e=>({content:[{type:"text",text:await j(e)}]}));u.registerTool("reload",{description:"Reload the React Native app via Metro."},async()=>(q(),{content:[{type:"text",text:await U()}]}));u.registerTool("resolve_stack",{description:"Resolve a stack trace from the buffer against the Metro source map, showing original file:line instead of bundle offsets. Optionally filter by error message substring.",inputSchema:G.shape},async e=>({content:[{type:"text",text:await J(e)}]}));u.registerTool("list_devices",{description:"List active iOS simulators and Android emulators. Use this to find available devices before taking screenshots or sending taps."},async()=>({content:[{type:"text",text:de()}]}));u.registerTool("screenshot",{description:"Take a screenshot of the active iOS simulator or Android emulator. Returns the image directly. Optionally specify platform ('ios' or 'android') or device_id if multiple devices are running.",inputSchema:ee.shape},async e=>{let n=te(e);if(n.type==="image"){let t=n.width&&n.height?`Screenshot dimensions: ${n.width}x${n.height}px. Use these exact coordinates for tap and swipe \u2014 no scaling needed.`:"Screenshot dimensions unknown.";return{content:[{type:"image",data:n.data,mimeType:n.mimeType},{type:"text",text:t}]}}return{content:[{type:"text",text:n.text}]}});u.registerTool("tap",{description:"Tap at x,y coordinates on the active simulator/emulator. Use screenshot first to determine coordinates. iOS requires idb (brew install idb-companion && pip3 install fb-idb). Android works via adb out of the box. Optionally specify platform or device_id.",inputSchema:ne.shape},async e=>({content:[{type:"text",text:oe(e)}]}));u.registerTool("swipe",{description:"Swipe from one coordinate to another. Requires idb on iOS (brew install idb-companion && pip3 install fb-idb). Android works via adb out of the box. Useful for scrolling lists or dismissing sheets.",inputSchema:re.shape},async e=>({content:[{type:"text",text:se(e)}]}));u.registerTool("input_text",{description:"Type text into the currently focused input field on the active simulator/emulator. Use tap to focus an input first, then call this tool. Works without requiring the on-screen keyboard to be visible. iOS requires idb (brew install idb-companion && pip3 install fb-idb). Android works via adb out of the box.",inputSchema:ce.shape},async e=>({content:[{type:"text",text:ue(e)}]}));u.registerTool("input_key",{description:"Send a special key press to the active simulator/emulator. Supported keys: enter, backspace, delete, tab, escape, home, end, back, space, up, down, left, right. Use after input_text to submit forms (enter) or correct mistakes (backspace).",inputSchema:ae.shape},async e=>({content:[{type:"text",text:pe(e)}]}));async function Qe(){c.start();let e=new Ve;await u.connect(e),process.on("SIGINT",()=>{c.stop(),process.exit(0)}),process.on("SIGTERM",()=>{c.stop(),process.exit(0)})}Qe().catch(e=>{process.stderr.write(`Fatal error: ${e}
18
+ Or: brew install facebook/fb/idb-companion`)}function xt(e,t,n){P(`adb -s "${e}" shell input tap ${t} ${n}`,{timeout:5e3,stdio:["ignore","ignore","pipe"]})}function St(e,t,n,r,o,i){if(fe()){R(e);let a=(i/1e3).toFixed(2);P(`idb ui swipe ${t} ${n} ${r} ${o} ${a} --udid "${e}"`,{timeout:i+5e3,stdio:["ignore","ignore","pipe"]});return}throw new Error(`iOS swipe requires idb (Facebook iOS Development Bridge).
19
+ Install via: brew install idb-companion && pip3 install fb-idb`)}function wt(e,t,n,r,o,i){P(`adb -s "${e}" shell input swipe ${t} ${n} ${r} ${o} ${i}`,{timeout:1e4,stdio:["ignore","ignore","pipe"]})}function ge(e){let t=h();if(!t.length)return"No active simulators or emulators found.";let n=x(e.platform,e.device_id);if(!n)return`No matching device found. Available: ${t.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return n.platform==="ios"?bt(n.id,e.x,e.y):xt(n.id,e.x,e.y),`Tapped (${e.x}, ${e.y}) on ${n.name} [${n.platform}].`}catch(r){return`Tap failed: ${r instanceof Error?r.message:String(r)}`}}function he(e){let t=h();if(!t.length)return"No active simulators or emulators found.";let n=x(e.platform,e.device_id);if(!n)return`No matching device found. Available: ${t.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return n.platform==="ios"?(St(n.id,e.x1,e.y1,e.x2,e.y2,e.duration_ms),`Swiped from (${e.x1}, ${e.y1}) to (${e.x2}, ${e.y2}) on ${n.name} [ios]. Note: iOS swipe simulation is limited \u2014 use Android for reliable swipe gestures.`):(wt(n.id,e.x1,e.y1,e.x2,e.y2,e.duration_ms),`Swiped from (${e.x1}, ${e.y1}) to (${e.x2}, ${e.y2}) on ${n.name} [android] over ${e.duration_ms}ms.`)}catch(r){return`Swipe failed: ${r instanceof Error?r.message:String(r)}`}}import{z as S}from"zod";import{execSync as E}from"child_process";var ve=S.object({text:S.string().describe("Text to type into the focused input field"),device_id:S.string().optional(),platform:S.enum(["ios","android"]).optional()}),ye=S.object({key:S.enum(["enter","backspace","delete","tab","escape","home","end","back","space","up","down","left","right"]).describe("Key to press"),device_id:S.string().optional(),platform:S.enum(["ios","android"]).optional()}),kt={enter:66,backspace:67,delete:67,tab:61,escape:111,home:3,end:123,back:4,space:62,up:19,down:20,left:21,right:22},_t={enter:40,backspace:42,delete:76,tab:43,escape:41,home:74,end:77,back:41,space:44,up:82,down:81,left:80,right:79};function be(){try{return E("which idb",{timeout:2e3,stdio:"ignore"}),!0}catch{return!1}}function $t(e,t){if(be()){R(e);let n=t.replace(/'/g,`'"'"'`);E(`idb ui text '${n}' --udid "${e}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]});return}throw new Error(`iOS text input requires idb.
20
+ Install via: brew install idb-companion && pip3 install fb-idb`)}function Mt(e,t){let n=t.replace(/\\/g,"\\\\").replace(/ /g,"%s").replace(/'/g,"\\'").replace(/"/g,'\\"');E(`adb -s "${e}" shell input text "${n}"`,{timeout:1e4,stdio:["ignore","ignore","pipe"]})}function Tt(e,t){if(be()){R(e);let n=_t[t];if(n===void 0)throw new Error(`Unknown key: ${t}`);E(`idb ui key ${n} --udid "${e}"`,{timeout:5e3,stdio:["ignore","ignore","pipe"]});return}throw new Error(`iOS key input requires idb.
21
+ Install via: brew install idb-companion && pip3 install fb-idb`)}function Rt(e,t){let n=kt[t];if(n===void 0)throw new Error(`Unknown key: ${t}`);E(`adb -s "${e}" shell input keyevent ${n}`,{timeout:5e3,stdio:["ignore","ignore","pipe"]})}function xe(e){let t=h();if(!t.length)return"No active simulators or emulators found.";let n=x(e.platform,e.device_id);if(!n)return`No matching device found. Available: ${t.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return n.platform==="ios"?$t(n.id,e.text):Mt(n.id,e.text),`Typed "${e.text}" on ${n.name} [${n.platform}].`}catch(r){return`input_text failed: ${r instanceof Error?r.message:String(r)}`}}function Se(e){let t=h();if(!t.length)return"No active simulators or emulators found.";let n=x(e.platform,e.device_id);if(!n)return`No matching device found. Available: ${t.map(r=>`${r.name} (${r.platform})`).join(", ")}`;try{return n.platform==="ios"?Tt(n.id,e.key):Rt(n.id,e.key),`Key "${e.key}" sent to ${n.name} [${n.platform}].`}catch(r){return`input_key failed: ${r instanceof Error?r.message:String(r)}`}}function we(){let e=h();if(!e.length)return"No active simulators or emulators found.\n\n- iOS: start a simulator via Xcode or `xcrun simctl boot <device>`\n- Android: start an emulator via Android Studio or `emulator -avd <name>`";let t=e.map(n=>`- ${n.name} [${n.platform}] id: ${n.id}`);return`Active devices (${e.length}):
22
+ ${t.join(`
23
+ `)}`}import{z as J}from"zod";var _e=J.object({code:J.string().min(1).max(2e4),timeout_ms:J.coerce.number().int().min(100).max(3e4).optional().default(5e3)});function ke(e){if(e===void 0)return"undefined";if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||e===null)return String(e);try{return JSON.stringify(e,null,2)}catch{return String(e)}}function Pt(e){if(!e)return"Unknown runtime error.";let t=typeof e.text=="string"?e.text:"Runtime evaluation failed.",n=e.exception&&typeof e.exception=="object"?e.exception:void 0,r=typeof n?.description=="string"?n.description:typeof n?.value=="string"?n.value:void 0;return r?`${t}
24
+ ${r}`:t}async function $e(e){let t=await c.evaluate(e.code,e.timeout_ms);if(t.error){let W=[t.error.message??"CDP error"];return t.error.data!==void 0&&W.push(ke(t.error.data)),`Evaluation failed.
25
+ ${W.join(`
26
+ `)}`}let n=t.result&&typeof t.result=="object"?t.result:void 0,r=n?.exceptionDetails&&typeof n.exceptionDetails=="object"?n.exceptionDetails:void 0;if(r)return`Evaluation threw.
27
+ ${Pt(r)}`;let o=n?.result&&typeof n.result=="object"?n.result:void 0;if(!o)return"Evaluation completed, but no result was returned.";let i=typeof o.type=="string"?o.type:"unknown",a=typeof o.subtype=="string"?o.subtype:void 0,u=o.value,l=typeof o.description=="string"?o.description:void 0,p=[`type: ${i}`];a&&p.push(`subtype: ${a}`);let g=u!==void 0?ke(u):l??"(no serializable value)";return`${p.join(", ")}
28
+ ${g}`}import{z as s}from"zod";var Et="globalThis.__EXPO_METRO_MCP__?.mmkv",Me=s.object({key:s.string().min(1).max(500),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Te=s.object({key:s.string().min(1).max(500),value:s.string().max(2e5),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Re=s.object({key:s.string().min(1).max(500),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Pe=s.object({timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Ee=s.object({key:s.string().min(1).max(500),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Oe=s.object({key:s.string().min(1).max(500),value:s.unknown(),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Ce=s.object({key:s.string().min(1).max(500),value:s.record(s.string(),s.unknown()),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Ne=s.object({key:s.string().min(1).max(500),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),De=s.object({key:s.string().min(1).max(500),state:s.record(s.string(),s.unknown()),version:s.number().int().optional(),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)}),Ie=s.object({key:s.string().min(1).max(500),state:s.record(s.string(),s.unknown()),version:s.number().int().optional(),timeout_ms:s.coerce.number().int().min(100).max(3e4).optional().default(5e3)});function d(e){return JSON.stringify(e)}function _(e){return JSON.stringify(e,null,2)}async function y(e,t){let n=await c.evaluate(e,t);if(n.error)throw new Error(n.error.message??"CDP evaluation failed.");let r=n.result&&typeof O(n.result)=="object"?O(n.result):void 0,o=r?.exceptionDetails&&typeof O(r.exceptionDetails)=="object"?r.exceptionDetails:void 0;if(o){let a=typeof o.text=="string"?o.text:"Runtime evaluation failed.",u=o.exception&&typeof O(o.exception)=="object"?o.exception:void 0,l=typeof u?.description=="string"?u.description:typeof u?.value=="string"?u.value:void 0;throw new Error(l?`${a}
29
+ ${l}`:a)}return(r?.result&&typeof O(r.result)=="object"?r.result:void 0)?.value}function O(e){return e&&typeof e=="object"?e:null}function b(e){return`(() => {
30
+ const mmkv = ${Et};
31
+ if (!mmkv) {
32
+ throw new Error("MMKV debug hook not found at globalThis.__EXPO_METRO_MCP__.mmkv");
33
+ }
34
+ if (typeof mmkv.getItem !== "function" || typeof mmkv.setItem !== "function" || typeof mmkv.removeItem !== "function" || typeof mmkv.getAllKeys !== "function") {
35
+ throw new Error("MMKV debug hook is present but missing one or more required methods: getItem, setItem, removeItem, getAllKeys");
36
+ }
37
+ return (${e})();
38
+ })()`}function I(e){return e==null?null:JSON.parse(e)}async function Ae(e){let t=await y(b(`() => ({ key: ${d(e.key)}, value: mmkv.getItem(${d(e.key)}) ?? null })`),e.timeout_ms);return _(t)}async function je(e){let t=await y(b(`() => {
39
+ mmkv.setItem(${d(e.key)}, ${d(e.value)});
40
+ return { ok: true, key: ${d(e.key)}, value: mmkv.getItem(${d(e.key)}) ?? null };
41
+ }`),e.timeout_ms);return _(t)}async function Le(e){let t=await y(b(`() => {
42
+ mmkv.removeItem(${d(e.key)});
43
+ return { ok: true, key: ${d(e.key)} };
44
+ }`),e.timeout_ms);return _(t)}async function ze(e){let t=await y(b("() => ({ keys: mmkv.getAllKeys() })"),e.timeout_ms);return _(t)}async function Je(e){let t=await y(b(`() => mmkv.getItem(${d(e.key)}) ?? null`),e.timeout_ms),n=I(t);return _({key:e.key,value:n})}async function C(e){let t=JSON.stringify(e.value),n=await y(b(`() => {
45
+ mmkv.setItem(${d(e.key)}, ${d(t)});
46
+ return { ok: true, key: ${d(e.key)}, value: JSON.parse(mmkv.getItem(${d(e.key)}) ?? "null") };
47
+ }`),e.timeout_ms);return _(n)}async function We(e){let t=await y(b(`() => mmkv.getItem(${d(e.key)}) ?? null`),e.timeout_ms),n=t==null?{}:I(t);if(!n||typeof n!="object"||Array.isArray(n))throw new Error("Existing MMKV value is not a JSON object, so merge would be sketchy. Use mmkv_set_json instead.");let r={...n,...e.value};return C({key:e.key,value:r,timeout_ms:e.timeout_ms})}async function Fe(e){let t=await y(b(`() => mmkv.getItem(${d(e.key)}) ?? null`),e.timeout_ms),n=I(t);return _({key:e.key,state:n?.state??null,version:typeof n?.version=="number"?n.version:null,raw:n})}async function Ke(e){let t={state:e.state,...e.version!==void 0?{version:e.version}:{}};return C({key:e.key,value:t,timeout_ms:e.timeout_ms})}async function Ge(e){let t=await y(b(`() => mmkv.getItem(${d(e.key)}) ?? null`),e.timeout_ms),n=t==null?null:I(t),r=n?.state;if(r!=null&&(typeof r!="object"||Array.isArray(r)))throw new Error("Existing persisted Zustand state is not an object, so merge would be sketchy. Use zustand_persist_set instead.");let o={state:{...r??{},...e.state},version:e.version??(typeof n?.version=="number"?n.version:void 0)};return C({key:e.key,value:o,timeout_ms:e.timeout_ms})}var m=new Ot({name:"expo-metro-mcp",version:"1.0.6"});m.registerTool("get_logs",{description:"Fetch recent logs from the Metro dev server buffer. Supports filtering by level and time.",inputSchema:K.shape},async e=>({content:[{type:"text",text:G(e)}]}));m.registerTool("get_errors",{description:"Fetch recent errors from the Metro dev server buffer, with stack traces.",inputSchema:U.shape},async e=>({content:[{type:"text",text:V(e)}]}));m.registerTool("get_status",{description:"Check the connection status of the Metro dev server and buffer statistics."},async()=>({content:[{type:"text",text:Z()}]}));m.registerTool("connect",{description:"Grab the CDP connection to the Metro dev server. Use this if get_status shows disconnected, or to take over the connection from React Native DevTools."},async()=>({content:[{type:"text",text:await c.grabConnection()}]}));m.registerTool("disconnect",{description:"Release the CDP connection so React Native DevTools can connect. Use this before switching to DevTools. Call connect when you want to reattach."},async()=>(c.disconnect(),{content:[{type:"text",text:"Disconnected. DevTools can now connect freely."}]}));m.registerTool("clear_logs",{description:"Clear the internal log buffer. Useful after resolving an issue."},async()=>({content:[{type:"text",text:H()}]}));m.registerTool("watch_logs",{description:"Listen for incoming logs for a short time window and return all collected entries.",inputSchema:q.shape},async e=>({content:[{type:"text",text:await X(e)}]}));m.registerTool("reload",{description:"Reload the React Native app via Metro."},async()=>(re(),{content:[{type:"text",text:await Y()}]}));m.registerTool("resolve_stack",{description:"Resolve a stack trace from the buffer against the Metro source map, showing original file:line instead of bundle offsets. Optionally filter by error message substring.",inputSchema:ne.shape},async e=>({content:[{type:"text",text:await oe(e)}]}));m.registerTool("list_devices",{description:"List active iOS simulators and Android emulators. Use this to find available devices before taking screenshots or sending taps."},async()=>({content:[{type:"text",text:we()}]}));m.registerTool("screenshot",{description:"Take a screenshot of the active iOS simulator or Android emulator. Returns the image directly. Optionally specify platform ('ios' or 'android') or device_id if multiple devices are running.",inputSchema:le.shape},async e=>{let t=me(e);if(t.type==="image"){let n=t.width&&t.height?`Screenshot dimensions: ${t.width}x${t.height}px. Use these exact coordinates for tap and swipe \u2014 no scaling needed.`:"Screenshot dimensions unknown.";return{content:[{type:"image",data:t.data,mimeType:t.mimeType},{type:"text",text:n}]}}return{content:[{type:"text",text:t.text}]}});m.registerTool("tap",{description:"Tap at x,y coordinates on the active simulator/emulator. Use screenshot first to determine coordinates. iOS requires idb (brew install idb-companion && pip3 install fb-idb). Android works via adb out of the box. Optionally specify platform or device_id.",inputSchema:pe.shape},async e=>({content:[{type:"text",text:ge(e)}]}));m.registerTool("swipe",{description:"Swipe from one coordinate to another. Requires idb on iOS (brew install idb-companion && pip3 install fb-idb). Android works via adb out of the box. Useful for scrolling lists or dismissing sheets.",inputSchema:de.shape},async e=>({content:[{type:"text",text:he(e)}]}));m.registerTool("input_text",{description:"Type text into the currently focused input field on the active simulator/emulator. Use tap to focus an input first, then call this tool. Works without requiring the on-screen keyboard to be visible. iOS requires idb (brew install idb-companion && pip3 install fb-idb). Android works via adb out of the box.",inputSchema:ve.shape},async e=>({content:[{type:"text",text:xe(e)}]}));m.registerTool("input_key",{description:"Send a special key press to the active simulator/emulator. Supported keys: enter, backspace, delete, tab, escape, home, end, back, space, up, down, left, right. Use after input_text to submit forms (enter) or correct mistakes (backspace).",inputSchema:ye.shape},async e=>({content:[{type:"text",text:Se(e)}]}));m.registerTool("evaluate",{description:"Run JavaScript inside the connected React Native app runtime via Metro CDP. Supports async expressions, so you can inspect globals, read or mutate state, trigger navigation, or call app helpers directly.",inputSchema:_e.shape},async e=>({content:[{type:"text",text:await $e(e)}]}));m.registerTool("mmkv_get",{description:"Read a value from the app's MMKV debug hook at globalThis.__EXPO_METRO_MCP__.mmkv. Returns JSON with key and value.",inputSchema:Me.shape},async e=>({content:[{type:"text",text:await Ae(e)}]}));m.registerTool("mmkv_set",{description:"Write a string value through the app's MMKV debug hook at globalThis.__EXPO_METRO_MCP__.mmkv. Useful for seeding persisted Zustand state before a screen renders.",inputSchema:Te.shape},async e=>({content:[{type:"text",text:await je(e)}]}));m.registerTool("mmkv_remove",{description:"Remove a key through the app's MMKV debug hook at globalThis.__EXPO_METRO_MCP__.mmkv.",inputSchema:Re.shape},async e=>({content:[{type:"text",text:await Le(e)}]}));m.registerTool("mmkv_keys",{description:"List all keys exposed by the app's MMKV debug hook at globalThis.__EXPO_METRO_MCP__.mmkv.",inputSchema:Pe.shape},async e=>({content:[{type:"text",text:await ze(e)}]}));m.registerTool("mmkv_get_json",{description:"Read a JSON value from the app's MMKV debug hook and parse it before returning it.",inputSchema:Ee.shape},async e=>({content:[{type:"text",text:await Je(e)}]}));m.registerTool("mmkv_set_json",{description:"Store any JSON-serializable value in MMKV through the app's debug hook. Safer than manually stringifying payloads in AI prompts.",inputSchema:Oe.shape},async e=>({content:[{type:"text",text:await C(e)}]}));m.registerTool("mmkv_merge_json",{description:"Merge a shallow JSON object into an existing MMKV JSON object. Good for patching persisted debug/config state without replacing the whole blob.",inputSchema:Ce.shape},async e=>({content:[{type:"text",text:await We(e)}]}));m.registerTool("zustand_persist_get",{description:"Read a persisted Zustand entry from MMKV and return its parsed state and version fields separately.",inputSchema:Ne.shape},async e=>({content:[{type:"text",text:await Fe(e)}]}));m.registerTool("zustand_persist_set",{description:"Write a persisted Zustand payload to MMKV in the canonical { state, version? } shape.",inputSchema:De.shape},async e=>({content:[{type:"text",text:await Ke(e)}]}));m.registerTool("zustand_persist_merge",{description:"Merge fields into the state object of an existing persisted Zustand MMKV entry, preserving version unless you override it.",inputSchema:Ie.shape},async e=>({content:[{type:"text",text:await Ge(e)}]}));async function Nt(){c.start();let e=new Ct;await m.connect(e),process.on("SIGINT",()=>{c.stop(),process.exit(0)}),process.on("SIGTERM",()=>{c.stop(),process.exit(0)})}Nt().catch(e=>{process.stderr.write(`Fatal error: ${e}
24
48
  `),process.exit(1)});
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@synnode/expo-metro-mcp",
3
- "version": "1.0.3",
4
- "description": "MCP server for Expo/React Native development — live logs, stack trace resolution, and simulator/emulator automation via CDP and platform CLIs.",
3
+ "version": "1.0.6",
4
+ "description": "MCP server for Expo/React Native development — live logs, stack trace resolution, runtime evaluation, and simulator/emulator automation via CDP and platform CLIs.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "expo-metro-mcp": "./dist/index.js"