metro-mcp 0.6.1 → 0.6.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/bin/metro-mcp.js +92 -42
- package/dist/index.js +92 -42
- package/dist/plugins/console.d.ts.map +1 -1
- package/dist/plugins/navigation.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin/metro-mcp.js
CHANGED
|
@@ -3740,7 +3740,7 @@ function extractCDPExceptionMessage(details, fallback = "Evaluation failed") {
|
|
|
3740
3740
|
// package.json
|
|
3741
3741
|
var package_default = {
|
|
3742
3742
|
name: "metro-mcp",
|
|
3743
|
-
version: "0.6.
|
|
3743
|
+
version: "0.6.3",
|
|
3744
3744
|
description: "Plugin-based MCP server for React Native/Expo runtime debugging, inspection, and automation via Metro/CDP",
|
|
3745
3745
|
homepage: "https://metromcp.dev",
|
|
3746
3746
|
repository: {
|
|
@@ -3941,45 +3941,103 @@ class DeviceBufferManager {
|
|
|
3941
3941
|
}
|
|
3942
3942
|
|
|
3943
3943
|
// src/plugins/console.ts
|
|
3944
|
+
function formatPreview(preview) {
|
|
3945
|
+
if (!preview.properties)
|
|
3946
|
+
return null;
|
|
3947
|
+
const props = preview.properties.map((p) => {
|
|
3948
|
+
if (p.type === "object" && p.valuePreview) {
|
|
3949
|
+
const nested = formatPreview(p.valuePreview);
|
|
3950
|
+
return `${p.name}: ${nested || p.value || "[object]"}`;
|
|
3951
|
+
}
|
|
3952
|
+
return `${p.name}: ${p.value}`;
|
|
3953
|
+
});
|
|
3954
|
+
const overflow = preview.overflow ? ", ..." : "";
|
|
3955
|
+
if (preview.subtype === "array")
|
|
3956
|
+
return `[${props.join(", ")}${overflow}]`;
|
|
3957
|
+
return `{${props.join(", ")}${overflow}}`;
|
|
3958
|
+
}
|
|
3959
|
+
function formatRemoteObject(obj) {
|
|
3960
|
+
if (obj.type === "string")
|
|
3961
|
+
return obj.value;
|
|
3962
|
+
if (obj.type === "number")
|
|
3963
|
+
return String(obj.value);
|
|
3964
|
+
if (obj.type === "boolean")
|
|
3965
|
+
return String(obj.value);
|
|
3966
|
+
if (obj.type === "undefined")
|
|
3967
|
+
return "undefined";
|
|
3968
|
+
if (obj.subtype === "null")
|
|
3969
|
+
return "null";
|
|
3970
|
+
if (obj.preview) {
|
|
3971
|
+
const formatted = formatPreview(obj.preview);
|
|
3972
|
+
if (formatted)
|
|
3973
|
+
return formatted;
|
|
3974
|
+
}
|
|
3975
|
+
if (obj.description)
|
|
3976
|
+
return obj.description;
|
|
3977
|
+
if (obj.value !== undefined)
|
|
3978
|
+
return JSON.stringify(obj.value);
|
|
3979
|
+
return obj.className || obj.type || "[object]";
|
|
3980
|
+
}
|
|
3944
3981
|
function formatCDPArgs(args) {
|
|
3945
3982
|
return args.map((arg) => {
|
|
3946
3983
|
if (typeof arg === "object" && arg !== null) {
|
|
3947
|
-
|
|
3948
|
-
if (remoteObj.type === "string")
|
|
3949
|
-
return remoteObj.value;
|
|
3950
|
-
if (remoteObj.type === "number")
|
|
3951
|
-
return String(remoteObj.value);
|
|
3952
|
-
if (remoteObj.type === "boolean")
|
|
3953
|
-
return String(remoteObj.value);
|
|
3954
|
-
if (remoteObj.type === "undefined")
|
|
3955
|
-
return "undefined";
|
|
3956
|
-
if (remoteObj.subtype === "null")
|
|
3957
|
-
return "null";
|
|
3958
|
-
if (remoteObj.description)
|
|
3959
|
-
return remoteObj.description;
|
|
3960
|
-
if (remoteObj.value !== undefined)
|
|
3961
|
-
return JSON.stringify(remoteObj.value);
|
|
3962
|
-
return remoteObj.className || remoteObj.type || "[object]";
|
|
3984
|
+
return formatRemoteObject(arg);
|
|
3963
3985
|
}
|
|
3964
3986
|
return String(arg);
|
|
3965
3987
|
}).join(" ");
|
|
3966
3988
|
}
|
|
3989
|
+
async function resolveRemoteObject(cdpSend, objectId) {
|
|
3990
|
+
try {
|
|
3991
|
+
const result = await cdpSend("Runtime.callFunctionOn", {
|
|
3992
|
+
objectId,
|
|
3993
|
+
functionDeclaration: 'function() { try { return JSON.stringify(this, null, 2); } catch(e) { return "[unserializable]"; } }',
|
|
3994
|
+
returnByValue: true
|
|
3995
|
+
});
|
|
3996
|
+
const inner = result.result;
|
|
3997
|
+
return inner?.value ? inner.value : null;
|
|
3998
|
+
} catch {
|
|
3999
|
+
return null;
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
async function formatCDPArgsDeep(cdpSend, args) {
|
|
4003
|
+
const parts = await Promise.all(args.map(async (arg) => {
|
|
4004
|
+
if (typeof arg !== "object" || arg === null)
|
|
4005
|
+
return String(arg);
|
|
4006
|
+
const remoteObj = arg;
|
|
4007
|
+
if (remoteObj.objectId && remoteObj.type === "object") {
|
|
4008
|
+
const deep = await resolveRemoteObject(cdpSend, remoteObj.objectId);
|
|
4009
|
+
if (deep)
|
|
4010
|
+
return deep;
|
|
4011
|
+
}
|
|
4012
|
+
return formatRemoteObject(remoteObj);
|
|
4013
|
+
}));
|
|
4014
|
+
return parts.join(" ");
|
|
4015
|
+
}
|
|
3967
4016
|
var consolePlugin = definePlugin({
|
|
3968
4017
|
name: "console",
|
|
3969
4018
|
description: "Console log collection and filtering",
|
|
3970
4019
|
async setup(ctx) {
|
|
3971
4020
|
const buffers = new DeviceBufferManager(500);
|
|
4021
|
+
const cdpSend = ctx.cdp.send.bind(ctx.cdp);
|
|
3972
4022
|
ctx.cdp.on("Runtime.consoleAPICalled", (params) => {
|
|
3973
4023
|
const key = ctx.getActiveDeviceKey();
|
|
3974
4024
|
if (!key)
|
|
3975
4025
|
return;
|
|
3976
4026
|
const args = params.args || [];
|
|
3977
|
-
|
|
4027
|
+
const entry = {
|
|
3978
4028
|
timestamp: Date.now(),
|
|
3979
4029
|
level: params.type,
|
|
3980
4030
|
message: formatCDPArgs(args),
|
|
3981
4031
|
stackTrace: params.stackTrace ? JSON.stringify(params.stackTrace.callFrames) : undefined
|
|
3982
|
-
}
|
|
4032
|
+
};
|
|
4033
|
+
buffers.getOrCreate(key).push(entry);
|
|
4034
|
+
const hasResolvable = args.some((arg) => typeof arg === "object" && arg !== null && arg.objectId);
|
|
4035
|
+
if (hasResolvable) {
|
|
4036
|
+
formatCDPArgsDeep(cdpSend, args).then((deep) => {
|
|
4037
|
+
if (deep !== entry.message)
|
|
4038
|
+
entry.message = deep;
|
|
4039
|
+
}).catch(() => {});
|
|
4040
|
+
}
|
|
3983
4041
|
});
|
|
3984
4042
|
ctx.events.on("bundle_transform_progressed", (event) => {
|
|
3985
4043
|
if (event.transformedFileCount === 1) {
|
|
@@ -6174,33 +6232,25 @@ var navigationPlugin = definePlugin({
|
|
|
6174
6232
|
return state;
|
|
6175
6233
|
}
|
|
6176
6234
|
});
|
|
6235
|
+
function getFocusedRoute(s) {
|
|
6236
|
+
const routes = s.routes;
|
|
6237
|
+
if (!routes)
|
|
6238
|
+
return null;
|
|
6239
|
+
const idx = s.index !== undefined ? s.index : routes.length - 1;
|
|
6240
|
+
const route = routes[idx];
|
|
6241
|
+
if (route.state && typeof route.state === "object") {
|
|
6242
|
+
return getFocusedRoute(route.state);
|
|
6243
|
+
}
|
|
6244
|
+
return { name: route.name, params: route.params || {}, key: route.key };
|
|
6245
|
+
}
|
|
6177
6246
|
ctx.registerTool("get_current_route", {
|
|
6178
6247
|
description: "Get the currently focused route name and params.",
|
|
6179
6248
|
parameters: z13.object({}),
|
|
6180
6249
|
handler: async () => {
|
|
6181
|
-
const
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
return getState()`)};
|
|
6186
|
-
|
|
6187
|
-
if (!state) return null;
|
|
6188
|
-
|
|
6189
|
-
// Walk to the deepest focused route
|
|
6190
|
-
function getFocusedRoute(s) {
|
|
6191
|
-
if (!s || !s.routes) return null;
|
|
6192
|
-
var idx = s.index !== undefined ? s.index : s.routes.length - 1;
|
|
6193
|
-
var route = s.routes[idx];
|
|
6194
|
-
if (route.state && route.state.routes) {
|
|
6195
|
-
return getFocusedRoute(route.state);
|
|
6196
|
-
}
|
|
6197
|
-
return { name: route.name, params: route.params || {}, key: route.key };
|
|
6198
|
-
}
|
|
6199
|
-
|
|
6200
|
-
return getFocusedRoute(state);
|
|
6201
|
-
})()
|
|
6202
|
-
`;
|
|
6203
|
-
const result = await ctx.evalInApp(expr, { awaitPromise: true });
|
|
6250
|
+
const state = await ctx.evalInApp(GET_NAV_STATE_EXPR, { awaitPromise: true });
|
|
6251
|
+
if (!state || typeof state !== "object")
|
|
6252
|
+
return "No focused route found.";
|
|
6253
|
+
const result = getFocusedRoute(state);
|
|
6204
6254
|
if (!result)
|
|
6205
6255
|
return "No focused route found.";
|
|
6206
6256
|
return result;
|
package/dist/index.js
CHANGED
|
@@ -3748,7 +3748,7 @@ function extractCDPExceptionMessage(details, fallback = "Evaluation failed") {
|
|
|
3748
3748
|
// package.json
|
|
3749
3749
|
var package_default = {
|
|
3750
3750
|
name: "metro-mcp",
|
|
3751
|
-
version: "0.6.
|
|
3751
|
+
version: "0.6.3",
|
|
3752
3752
|
description: "Plugin-based MCP server for React Native/Expo runtime debugging, inspection, and automation via Metro/CDP",
|
|
3753
3753
|
homepage: "https://metromcp.dev",
|
|
3754
3754
|
repository: {
|
|
@@ -3944,45 +3944,103 @@ class DeviceBufferManager {
|
|
|
3944
3944
|
}
|
|
3945
3945
|
|
|
3946
3946
|
// src/plugins/console.ts
|
|
3947
|
+
function formatPreview(preview) {
|
|
3948
|
+
if (!preview.properties)
|
|
3949
|
+
return null;
|
|
3950
|
+
const props = preview.properties.map((p) => {
|
|
3951
|
+
if (p.type === "object" && p.valuePreview) {
|
|
3952
|
+
const nested = formatPreview(p.valuePreview);
|
|
3953
|
+
return `${p.name}: ${nested || p.value || "[object]"}`;
|
|
3954
|
+
}
|
|
3955
|
+
return `${p.name}: ${p.value}`;
|
|
3956
|
+
});
|
|
3957
|
+
const overflow = preview.overflow ? ", ..." : "";
|
|
3958
|
+
if (preview.subtype === "array")
|
|
3959
|
+
return `[${props.join(", ")}${overflow}]`;
|
|
3960
|
+
return `{${props.join(", ")}${overflow}}`;
|
|
3961
|
+
}
|
|
3962
|
+
function formatRemoteObject(obj) {
|
|
3963
|
+
if (obj.type === "string")
|
|
3964
|
+
return obj.value;
|
|
3965
|
+
if (obj.type === "number")
|
|
3966
|
+
return String(obj.value);
|
|
3967
|
+
if (obj.type === "boolean")
|
|
3968
|
+
return String(obj.value);
|
|
3969
|
+
if (obj.type === "undefined")
|
|
3970
|
+
return "undefined";
|
|
3971
|
+
if (obj.subtype === "null")
|
|
3972
|
+
return "null";
|
|
3973
|
+
if (obj.preview) {
|
|
3974
|
+
const formatted = formatPreview(obj.preview);
|
|
3975
|
+
if (formatted)
|
|
3976
|
+
return formatted;
|
|
3977
|
+
}
|
|
3978
|
+
if (obj.description)
|
|
3979
|
+
return obj.description;
|
|
3980
|
+
if (obj.value !== undefined)
|
|
3981
|
+
return JSON.stringify(obj.value);
|
|
3982
|
+
return obj.className || obj.type || "[object]";
|
|
3983
|
+
}
|
|
3947
3984
|
function formatCDPArgs(args) {
|
|
3948
3985
|
return args.map((arg) => {
|
|
3949
3986
|
if (typeof arg === "object" && arg !== null) {
|
|
3950
|
-
|
|
3951
|
-
if (remoteObj.type === "string")
|
|
3952
|
-
return remoteObj.value;
|
|
3953
|
-
if (remoteObj.type === "number")
|
|
3954
|
-
return String(remoteObj.value);
|
|
3955
|
-
if (remoteObj.type === "boolean")
|
|
3956
|
-
return String(remoteObj.value);
|
|
3957
|
-
if (remoteObj.type === "undefined")
|
|
3958
|
-
return "undefined";
|
|
3959
|
-
if (remoteObj.subtype === "null")
|
|
3960
|
-
return "null";
|
|
3961
|
-
if (remoteObj.description)
|
|
3962
|
-
return remoteObj.description;
|
|
3963
|
-
if (remoteObj.value !== undefined)
|
|
3964
|
-
return JSON.stringify(remoteObj.value);
|
|
3965
|
-
return remoteObj.className || remoteObj.type || "[object]";
|
|
3987
|
+
return formatRemoteObject(arg);
|
|
3966
3988
|
}
|
|
3967
3989
|
return String(arg);
|
|
3968
3990
|
}).join(" ");
|
|
3969
3991
|
}
|
|
3992
|
+
async function resolveRemoteObject(cdpSend, objectId) {
|
|
3993
|
+
try {
|
|
3994
|
+
const result = await cdpSend("Runtime.callFunctionOn", {
|
|
3995
|
+
objectId,
|
|
3996
|
+
functionDeclaration: 'function() { try { return JSON.stringify(this, null, 2); } catch(e) { return "[unserializable]"; } }',
|
|
3997
|
+
returnByValue: true
|
|
3998
|
+
});
|
|
3999
|
+
const inner = result.result;
|
|
4000
|
+
return inner?.value ? inner.value : null;
|
|
4001
|
+
} catch {
|
|
4002
|
+
return null;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
async function formatCDPArgsDeep(cdpSend, args) {
|
|
4006
|
+
const parts = await Promise.all(args.map(async (arg) => {
|
|
4007
|
+
if (typeof arg !== "object" || arg === null)
|
|
4008
|
+
return String(arg);
|
|
4009
|
+
const remoteObj = arg;
|
|
4010
|
+
if (remoteObj.objectId && remoteObj.type === "object") {
|
|
4011
|
+
const deep = await resolveRemoteObject(cdpSend, remoteObj.objectId);
|
|
4012
|
+
if (deep)
|
|
4013
|
+
return deep;
|
|
4014
|
+
}
|
|
4015
|
+
return formatRemoteObject(remoteObj);
|
|
4016
|
+
}));
|
|
4017
|
+
return parts.join(" ");
|
|
4018
|
+
}
|
|
3970
4019
|
var consolePlugin = definePlugin({
|
|
3971
4020
|
name: "console",
|
|
3972
4021
|
description: "Console log collection and filtering",
|
|
3973
4022
|
async setup(ctx) {
|
|
3974
4023
|
const buffers = new DeviceBufferManager(500);
|
|
4024
|
+
const cdpSend = ctx.cdp.send.bind(ctx.cdp);
|
|
3975
4025
|
ctx.cdp.on("Runtime.consoleAPICalled", (params) => {
|
|
3976
4026
|
const key = ctx.getActiveDeviceKey();
|
|
3977
4027
|
if (!key)
|
|
3978
4028
|
return;
|
|
3979
4029
|
const args = params.args || [];
|
|
3980
|
-
|
|
4030
|
+
const entry = {
|
|
3981
4031
|
timestamp: Date.now(),
|
|
3982
4032
|
level: params.type,
|
|
3983
4033
|
message: formatCDPArgs(args),
|
|
3984
4034
|
stackTrace: params.stackTrace ? JSON.stringify(params.stackTrace.callFrames) : undefined
|
|
3985
|
-
}
|
|
4035
|
+
};
|
|
4036
|
+
buffers.getOrCreate(key).push(entry);
|
|
4037
|
+
const hasResolvable = args.some((arg) => typeof arg === "object" && arg !== null && arg.objectId);
|
|
4038
|
+
if (hasResolvable) {
|
|
4039
|
+
formatCDPArgsDeep(cdpSend, args).then((deep) => {
|
|
4040
|
+
if (deep !== entry.message)
|
|
4041
|
+
entry.message = deep;
|
|
4042
|
+
}).catch(() => {});
|
|
4043
|
+
}
|
|
3986
4044
|
});
|
|
3987
4045
|
ctx.events.on("bundle_transform_progressed", (event) => {
|
|
3988
4046
|
if (event.transformedFileCount === 1) {
|
|
@@ -6177,33 +6235,25 @@ var navigationPlugin = definePlugin({
|
|
|
6177
6235
|
return state;
|
|
6178
6236
|
}
|
|
6179
6237
|
});
|
|
6238
|
+
function getFocusedRoute(s) {
|
|
6239
|
+
const routes = s.routes;
|
|
6240
|
+
if (!routes)
|
|
6241
|
+
return null;
|
|
6242
|
+
const idx = s.index !== undefined ? s.index : routes.length - 1;
|
|
6243
|
+
const route = routes[idx];
|
|
6244
|
+
if (route.state && typeof route.state === "object") {
|
|
6245
|
+
return getFocusedRoute(route.state);
|
|
6246
|
+
}
|
|
6247
|
+
return { name: route.name, params: route.params || {}, key: route.key };
|
|
6248
|
+
}
|
|
6180
6249
|
ctx.registerTool("get_current_route", {
|
|
6181
6250
|
description: "Get the currently focused route name and params.",
|
|
6182
6251
|
parameters: z13.object({}),
|
|
6183
6252
|
handler: async () => {
|
|
6184
|
-
const
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
return getState()`)};
|
|
6189
|
-
|
|
6190
|
-
if (!state) return null;
|
|
6191
|
-
|
|
6192
|
-
// Walk to the deepest focused route
|
|
6193
|
-
function getFocusedRoute(s) {
|
|
6194
|
-
if (!s || !s.routes) return null;
|
|
6195
|
-
var idx = s.index !== undefined ? s.index : s.routes.length - 1;
|
|
6196
|
-
var route = s.routes[idx];
|
|
6197
|
-
if (route.state && route.state.routes) {
|
|
6198
|
-
return getFocusedRoute(route.state);
|
|
6199
|
-
}
|
|
6200
|
-
return { name: route.name, params: route.params || {}, key: route.key };
|
|
6201
|
-
}
|
|
6202
|
-
|
|
6203
|
-
return getFocusedRoute(state);
|
|
6204
|
-
})()
|
|
6205
|
-
`;
|
|
6206
|
-
const result = await ctx.evalInApp(expr, { awaitPromise: true });
|
|
6253
|
+
const state = await ctx.evalInApp(GET_NAV_STATE_EXPR, { awaitPromise: true });
|
|
6254
|
+
if (!state || typeof state !== "object")
|
|
6255
|
+
return "No focused route found.";
|
|
6256
|
+
const result = getFocusedRoute(state);
|
|
6207
6257
|
if (!result)
|
|
6208
6258
|
return "No focused route found.";
|
|
6209
6259
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/plugins/console.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/plugins/console.ts"],"names":[],"mappings":"AAuHA,eAAO,MAAM,aAAa,yCAiIxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/plugins/navigation.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/plugins/navigation.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,yCA+J3B,CAAC"}
|
package/package.json
CHANGED