@synnode/expo-metro-mcp 1.0.2 → 1.0.3
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/index.js +16 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{McpServer as
|
|
3
|
-
`)}import{z as
|
|
4
|
-
${
|
|
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(`
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
`)}function
|
|
9
|
-
`)}import
|
|
10
|
-
`)[0]}`;if(!
|
|
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}
|
|
11
11
|
|
|
12
|
-
No stack frames available.`;let
|
|
12
|
+
No stack frames available.`;let i=Ae(t.rawMessage);if(!i.length)return`${r}
|
|
13
13
|
|
|
14
|
-
No stack frames available.`;let
|
|
15
|
-
`)}import{z as
|
|
16
|
-
`).slice(1);for(let r of
|
|
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).
|
|
17
17
|
Install via: brew install idb-companion && pip3 install fb-idb
|
|
18
|
-
Or: brew install facebook/fb/idb-companion`)}function
|
|
19
|
-
Install via: brew install idb-companion && pip3 install fb-idb`)}function
|
|
20
|
-
|
|
21
|
-
|
|
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}
|
|
22
24
|
`),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synnode/expo-metro-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "MCP server for Expo/React Native development — live logs, stack trace resolution, and simulator/emulator automation via CDP and platform CLIs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|