react-native-ai-debugger 1.0.45 → 1.0.47
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/README.md +8 -0
- package/build/core/connection.d.ts +2 -1
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +198 -26
- package/build/core/connection.js.map +1 -1
- package/build/core/guides.d.ts +14 -0
- package/build/core/guides.d.ts.map +1 -0
- package/build/core/guides.js +240 -0
- package/build/core/guides.js.map +1 -0
- package/build/index.js +162 -235
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { getGuideOverview, getGuideByTopic, getAvailableTopics } from "./core/guides.js";
|
|
5
6
|
import { logBuffer, networkBuffer, bundleErrorBuffer, connectedApps, getActiveSimulatorUdid, scanMetroPorts, fetchDevices, selectMainDevice, connectToDevice, getConnectedApps, executeInApp, listDebugGlobals, inspectGlobal, reloadApp,
|
|
6
7
|
// React Component Inspection
|
|
7
8
|
getComponentTree, getScreenLayout, inspectComponent, findComponents, pressElement, inspectAtPoint, toggleElementInspector, isInspectorActive, getInspectorSelection, getFirstConnectedApp, getLogs, searchLogs, getLogSummary, getNetworkRequests, searchNetworkRequests, getNetworkStats, formatRequestDetails,
|
|
@@ -41,6 +42,8 @@ formatLogsAsTonl, formatNetworkAsTonl } from "./core/index.js";
|
|
|
41
42
|
const server = new McpServer({
|
|
42
43
|
name: "react-native-ai-debugger",
|
|
43
44
|
version: "1.0.0"
|
|
45
|
+
}, {
|
|
46
|
+
instructions: "React Native debugging MCP server. Call get_usage_guide to learn recommended workflows for all tools. Quick start: scan_metro → get_logs / search_logs (console debugging) → ios_screenshot → get_inspector_selection(x, y) (identify components)."
|
|
44
47
|
});
|
|
45
48
|
// ============================================================================
|
|
46
49
|
// Telemetry Wrapper
|
|
@@ -50,15 +53,15 @@ const server = new McpServer({
|
|
|
50
53
|
* Only needs the first ~2KB of the image to find dimensions.
|
|
51
54
|
*/
|
|
52
55
|
function getJpegDimensions(buffer) {
|
|
53
|
-
if (buffer.length < 4 || buffer[0] !==
|
|
56
|
+
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8)
|
|
54
57
|
return null;
|
|
55
58
|
let offset = 2;
|
|
56
59
|
while (offset < buffer.length - 9) {
|
|
57
|
-
if (buffer[offset] !==
|
|
60
|
+
if (buffer[offset] !== 0xff)
|
|
58
61
|
return null;
|
|
59
62
|
const marker = buffer[offset + 1];
|
|
60
63
|
// SOF markers: C0-CF except C4 (DHT) and CC (DAC)
|
|
61
|
-
if (marker >=
|
|
64
|
+
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xcc) {
|
|
62
65
|
const height = buffer.readUInt16BE(offset + 5);
|
|
63
66
|
const width = buffer.readUInt16BE(offset + 7);
|
|
64
67
|
return { width, height };
|
|
@@ -118,13 +121,15 @@ function registerToolWithTelemetry(toolName, config, handler) {
|
|
|
118
121
|
try {
|
|
119
122
|
inputTokens = Math.ceil(JSON.stringify(args).length / 4);
|
|
120
123
|
}
|
|
121
|
-
catch {
|
|
124
|
+
catch {
|
|
125
|
+
/* circular refs — leave undefined */
|
|
126
|
+
}
|
|
122
127
|
try {
|
|
123
128
|
const result = await handler(args);
|
|
124
129
|
// Check if result indicates an error
|
|
125
130
|
if (result?.isError) {
|
|
126
131
|
success = false;
|
|
127
|
-
errorMessage = result.content?.[0]?.text ||
|
|
132
|
+
errorMessage = result.content?.[0]?.text || "Unknown error";
|
|
128
133
|
// Extract error context if provided (e.g., the expression that caused a syntax error)
|
|
129
134
|
errorContext = result._errorContext;
|
|
130
135
|
}
|
|
@@ -155,6 +160,38 @@ function registerToolWithTelemetry(toolName, config, handler) {
|
|
|
155
160
|
});
|
|
156
161
|
}
|
|
157
162
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
163
|
+
// Tool: Usage guide for agents
|
|
164
|
+
registerToolWithTelemetry("get_usage_guide", {
|
|
165
|
+
description: "Get recommended workflows and best practices for using the debugging tools. Call without parameters to see all available topics with short descriptions. Call with a topic parameter to get the full guide for that topic.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
topic: z
|
|
168
|
+
.string()
|
|
169
|
+
.optional()
|
|
170
|
+
.describe("Topic to get the full guide for. Available topics: setup, inspect, layout, interact, logs, network, state, bundle. Omit to see the overview of all topics.")
|
|
171
|
+
}
|
|
172
|
+
}, async ({ topic }) => {
|
|
173
|
+
if (!topic) {
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: "text", text: getGuideOverview() }]
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const guide = getGuideByTopic(topic);
|
|
179
|
+
if (!guide) {
|
|
180
|
+
const available = getAvailableTopics().join(", ");
|
|
181
|
+
return {
|
|
182
|
+
content: [
|
|
183
|
+
{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: `Unknown topic: "${topic}". Available topics: ${available}`
|
|
186
|
+
}
|
|
187
|
+
],
|
|
188
|
+
isError: true
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: "text", text: guide }]
|
|
193
|
+
};
|
|
194
|
+
});
|
|
158
195
|
// Tool: Scan for Metro servers
|
|
159
196
|
registerToolWithTelemetry("scan_metro", {
|
|
160
197
|
description: "Scan for running Metro bundler servers and automatically connect to any found React Native apps. This is typically the FIRST tool to call when starting a debugging session - it establishes the connection needed for other tools like get_logs, list_debug_globals, execute_in_app, and reload_app.",
|
|
@@ -380,15 +417,19 @@ registerToolWithTelemetry("ensure_connection", {
|
|
|
380
417
|
registerToolWithTelemetry("get_logs", {
|
|
381
418
|
description: "Retrieve console logs from connected React Native app. Tip: Use summary=true first for a quick overview (counts by level + last 5 messages), then fetch specific logs as needed.",
|
|
382
419
|
inputSchema: {
|
|
383
|
-
maxLogs: z.coerce
|
|
420
|
+
maxLogs: z.coerce
|
|
421
|
+
.number()
|
|
422
|
+
.optional()
|
|
423
|
+
.default(50)
|
|
424
|
+
.describe("Maximum number of logs to return (default: 50)"),
|
|
384
425
|
level: z
|
|
385
426
|
.enum(["all", "log", "warn", "error", "info", "debug"])
|
|
386
427
|
.optional()
|
|
387
428
|
.default("all")
|
|
388
429
|
.describe("Filter by log level (default: all)"),
|
|
389
430
|
startFromText: z.string().optional().describe("Start from the first log line containing this text"),
|
|
390
|
-
maxMessageLength: z
|
|
391
|
-
.
|
|
431
|
+
maxMessageLength: z.coerce
|
|
432
|
+
.number()
|
|
392
433
|
.optional()
|
|
393
434
|
.default(500)
|
|
394
435
|
.describe("Max characters per message (default: 500, set to 0 for unlimited). Tip: Use lower values for overview, higher when debugging specific data structures."),
|
|
@@ -426,7 +467,13 @@ registerToolWithTelemetry("get_logs", {
|
|
|
426
467
|
]
|
|
427
468
|
};
|
|
428
469
|
}
|
|
429
|
-
const { logs, count, formatted } = getLogs(logBuffer, {
|
|
470
|
+
const { logs, count, formatted } = getLogs(logBuffer, {
|
|
471
|
+
maxLogs,
|
|
472
|
+
level,
|
|
473
|
+
startFromText,
|
|
474
|
+
maxMessageLength,
|
|
475
|
+
verbose
|
|
476
|
+
});
|
|
430
477
|
// Check connection health
|
|
431
478
|
let connectionWarning = "";
|
|
432
479
|
if (count === 0) {
|
|
@@ -445,7 +492,7 @@ registerToolWithTelemetry("get_logs", {
|
|
|
445
492
|
let gapWarning = "";
|
|
446
493
|
if (recentGaps.length > 0) {
|
|
447
494
|
const latestGap = recentGaps[recentGaps.length - 1];
|
|
448
|
-
const gapDuration = latestGap.durationMs ||
|
|
495
|
+
const gapDuration = latestGap.durationMs || Date.now() - latestGap.disconnectedAt.getTime();
|
|
449
496
|
if (latestGap.reconnectedAt) {
|
|
450
497
|
const secAgo = Math.round((Date.now() - latestGap.reconnectedAt.getTime()) / 1000);
|
|
451
498
|
gapWarning = `\n\n[WARNING] Connection was restored ${secAgo}s ago. Some logs may have been missed during the ${formatDuration(gapDuration)} gap.`;
|
|
@@ -481,17 +528,17 @@ registerToolWithTelemetry("search_logs", {
|
|
|
481
528
|
description: "Search console logs for text (case-insensitive)",
|
|
482
529
|
inputSchema: {
|
|
483
530
|
text: z.string().describe("Text to search for in log messages"),
|
|
484
|
-
maxResults: z.coerce
|
|
485
|
-
|
|
486
|
-
.
|
|
531
|
+
maxResults: z.coerce
|
|
532
|
+
.number()
|
|
533
|
+
.optional()
|
|
534
|
+
.default(50)
|
|
535
|
+
.describe("Maximum number of results to return (default: 50)"),
|
|
536
|
+
maxMessageLength: z.coerce
|
|
537
|
+
.number()
|
|
487
538
|
.optional()
|
|
488
539
|
.default(500)
|
|
489
540
|
.describe("Max characters per message (default: 500, set to 0 for unlimited)"),
|
|
490
|
-
verbose: z
|
|
491
|
-
.boolean()
|
|
492
|
-
.optional()
|
|
493
|
-
.default(false)
|
|
494
|
-
.describe("Disable all truncation and return full messages"),
|
|
541
|
+
verbose: z.boolean().optional().default(false).describe("Disable all truncation and return full messages"),
|
|
495
542
|
format: z
|
|
496
543
|
.enum(["text", "tonl"])
|
|
497
544
|
.optional()
|
|
@@ -617,10 +664,16 @@ registerToolWithTelemetry("execute_in_app", {
|
|
|
617
664
|
"GOOD examples: `__DEV__`, `__APOLLO_CLIENT__.cache.extract()`, `__EXPO_ROUTER__.navigate('/settings')`\n" +
|
|
618
665
|
"BAD examples: `async () => { await fetch(...) }`, `require('react-native')`, `console.log('\\u{1F600}')`",
|
|
619
666
|
inputSchema: {
|
|
620
|
-
expression: z
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
667
|
+
expression: z
|
|
668
|
+
.string()
|
|
669
|
+
.describe("JavaScript expression to execute. Must be valid Hermes syntax — no require(), no async/await, no emoji/non-ASCII in strings. Use globals discovered via list_debug_globals."),
|
|
670
|
+
awaitPromise: z.coerce
|
|
671
|
+
.boolean()
|
|
672
|
+
.optional()
|
|
673
|
+
.default(true)
|
|
674
|
+
.describe("Whether to await promises (default: true)"),
|
|
675
|
+
maxResultLength: z.coerce
|
|
676
|
+
.number()
|
|
624
677
|
.optional()
|
|
625
678
|
.default(2000)
|
|
626
679
|
.describe("Max characters in result (default: 2000, set to 0 for unlimited). Tip: For large objects like Redux stores, use inspect_global instead or set higher limit."),
|
|
@@ -637,7 +690,8 @@ registerToolWithTelemetry("execute_in_app", {
|
|
|
637
690
|
// If the error is a ReferenceError (accessing a global that doesn't exist),
|
|
638
691
|
// guide the agent to expose the variable as a global first
|
|
639
692
|
if (result.error?.includes("ReferenceError")) {
|
|
640
|
-
errorText +=
|
|
693
|
+
errorText +=
|
|
694
|
+
"\n\nNOTE: This variable is not exposed as a global. To access it, first assign it to a global variable in your app code (e.g., `globalThis.__MY_VAR__ = myVar;`), then use execute_in_app to read `__MY_VAR__`. You can also use list_debug_globals to see what globals ARE currently available.";
|
|
641
695
|
}
|
|
642
696
|
return {
|
|
643
697
|
content: [
|
|
@@ -654,7 +708,8 @@ registerToolWithTelemetry("execute_in_app", {
|
|
|
654
708
|
let resultText = result.result ?? "undefined";
|
|
655
709
|
// Apply truncation unless verbose or unlimited
|
|
656
710
|
if (!verbose && maxResultLength > 0 && resultText.length > maxResultLength) {
|
|
657
|
-
resultText =
|
|
711
|
+
resultText =
|
|
712
|
+
resultText.slice(0, maxResultLength) + `... [truncated: ${result.result?.length ?? 0} chars total]`;
|
|
658
713
|
}
|
|
659
714
|
return {
|
|
660
715
|
content: [
|
|
@@ -706,7 +761,7 @@ registerToolWithTelemetry("inspect_global", {
|
|
|
706
761
|
// If the error is a ReferenceError (accessing a global that doesn't exist),
|
|
707
762
|
// guide the agent to expose the variable as a global first
|
|
708
763
|
if (result.error?.includes("ReferenceError")) {
|
|
709
|
-
errorText += `\n\nNOTE: '${objectName}' is not exposed as a global variable. To inspect it, first assign it to a global in your app code (e.g., \`globalThis.${objectName} = ${objectName.replace(/^__/,
|
|
764
|
+
errorText += `\n\nNOTE: '${objectName}' is not exposed as a global variable. To inspect it, first assign it to a global in your app code (e.g., \`globalThis.${objectName} = ${objectName.replace(/^__/, "").replace(/__$/, "")};\`), then call inspect_global again. Use list_debug_globals to see what globals ARE currently available.`;
|
|
710
765
|
}
|
|
711
766
|
return {
|
|
712
767
|
content: [
|
|
@@ -770,7 +825,15 @@ registerToolWithTelemetry("get_component_tree", {
|
|
|
770
825
|
.describe("Output format: 'json' or 'tonl' (default, compact indented tree). Ignored if structureOnly=true.")
|
|
771
826
|
}
|
|
772
827
|
}, async ({ focusedOnly, structureOnly, maxDepth, includeProps, includeStyles, hideInternals, format }) => {
|
|
773
|
-
const result = await getComponentTree({
|
|
828
|
+
const result = await getComponentTree({
|
|
829
|
+
focusedOnly,
|
|
830
|
+
structureOnly,
|
|
831
|
+
maxDepth,
|
|
832
|
+
includeProps,
|
|
833
|
+
includeStyles,
|
|
834
|
+
hideInternals,
|
|
835
|
+
format
|
|
836
|
+
});
|
|
774
837
|
if (!result.success) {
|
|
775
838
|
return {
|
|
776
839
|
content: [
|
|
@@ -860,21 +923,13 @@ registerToolWithTelemetry("inspect_component", {
|
|
|
860
923
|
.optional()
|
|
861
924
|
.default(true)
|
|
862
925
|
.describe("Include component state/hooks (default: true)"),
|
|
863
|
-
includeChildren: z
|
|
864
|
-
.boolean()
|
|
865
|
-
.optional()
|
|
866
|
-
.default(false)
|
|
867
|
-
.describe("Include children component tree"),
|
|
926
|
+
includeChildren: z.boolean().optional().default(false).describe("Include children component tree"),
|
|
868
927
|
childrenDepth: z
|
|
869
928
|
.number()
|
|
870
929
|
.optional()
|
|
871
930
|
.default(1)
|
|
872
931
|
.describe("How many levels deep to show children (default: 1 = direct children only, 2+ = nested tree)"),
|
|
873
|
-
shortPath: z
|
|
874
|
-
.boolean()
|
|
875
|
-
.optional()
|
|
876
|
-
.default(true)
|
|
877
|
-
.describe("Show only last 3 path segments (default: true)"),
|
|
932
|
+
shortPath: z.boolean().optional().default(true).describe("Show only last 3 path segments (default: true)"),
|
|
878
933
|
simplifyHooks: z
|
|
879
934
|
.boolean()
|
|
880
935
|
.optional()
|
|
@@ -882,7 +937,14 @@ registerToolWithTelemetry("inspect_component", {
|
|
|
882
937
|
.describe("Simplify hooks output by hiding effects and reducing depth (default: true)")
|
|
883
938
|
}
|
|
884
939
|
}, async ({ componentName, index, includeState, includeChildren, childrenDepth, shortPath, simplifyHooks }) => {
|
|
885
|
-
const result = await inspectComponent(componentName, {
|
|
940
|
+
const result = await inspectComponent(componentName, {
|
|
941
|
+
index,
|
|
942
|
+
includeState,
|
|
943
|
+
includeChildren,
|
|
944
|
+
childrenDepth,
|
|
945
|
+
shortPath,
|
|
946
|
+
simplifyHooks
|
|
947
|
+
});
|
|
886
948
|
if (!result.success) {
|
|
887
949
|
return {
|
|
888
950
|
content: [
|
|
@@ -910,21 +972,13 @@ registerToolWithTelemetry("find_components", {
|
|
|
910
972
|
pattern: z
|
|
911
973
|
.string()
|
|
912
974
|
.describe("Regex pattern to match component names (case-insensitive). Examples: 'Button', 'Screen$', 'List.*Item'"),
|
|
913
|
-
maxResults: z
|
|
914
|
-
.number()
|
|
915
|
-
.optional()
|
|
916
|
-
.default(20)
|
|
917
|
-
.describe("Maximum number of results to return (default: 20)"),
|
|
975
|
+
maxResults: z.number().optional().default(20).describe("Maximum number of results to return (default: 20)"),
|
|
918
976
|
includeLayout: z
|
|
919
977
|
.boolean()
|
|
920
978
|
.optional()
|
|
921
979
|
.default(false)
|
|
922
980
|
.describe("Include layout styles (padding, margin, flex) for each matched component"),
|
|
923
|
-
shortPath: z
|
|
924
|
-
.boolean()
|
|
925
|
-
.optional()
|
|
926
|
-
.default(true)
|
|
927
|
-
.describe("Show only last 3 path segments (default: true)"),
|
|
981
|
+
shortPath: z.boolean().optional().default(true).describe("Show only last 3 path segments (default: true)"),
|
|
928
982
|
summary: z
|
|
929
983
|
.boolean()
|
|
930
984
|
.optional()
|
|
@@ -970,16 +1024,13 @@ registerToolWithTelemetry("press_element", {
|
|
|
970
1024
|
.string()
|
|
971
1025
|
.optional()
|
|
972
1026
|
.describe("Case-insensitive partial match on the element's text content (e.g., 'Submit', 'Log in'). ASCII only — non-Latin characters (Cyrillic, CJK, etc.) cause Hermes parse errors. Use testID or component for localized UIs."),
|
|
973
|
-
testID: z
|
|
974
|
-
.string()
|
|
975
|
-
.optional()
|
|
976
|
-
.describe("Exact match on the element's testID prop"),
|
|
1027
|
+
testID: z.string().optional().describe("Exact match on the element's testID prop"),
|
|
977
1028
|
component: z
|
|
978
1029
|
.string()
|
|
979
1030
|
.optional()
|
|
980
1031
|
.describe("Case-insensitive partial match on the component's displayName or name (e.g., 'Button', 'MenuItem')"),
|
|
981
|
-
index: z
|
|
982
|
-
.
|
|
1032
|
+
index: z.coerce
|
|
1033
|
+
.number()
|
|
983
1034
|
.optional()
|
|
984
1035
|
.default(0)
|
|
985
1036
|
.describe("Zero-based index when multiple elements match (default: 0). If unsure, omit to press the first match.")
|
|
@@ -1078,7 +1129,7 @@ registerToolWithTelemetry("get_inspector_selection", {
|
|
|
1078
1129
|
if (!inspectorActive) {
|
|
1079
1130
|
await toggleElementInspector();
|
|
1080
1131
|
// Wait for inspector to initialize
|
|
1081
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
1132
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1082
1133
|
}
|
|
1083
1134
|
// Detect platform from connected app
|
|
1084
1135
|
const app = getFirstConnectedApp();
|
|
@@ -1088,10 +1139,10 @@ registerToolWithTelemetry("get_inspector_selection", {
|
|
|
1088
1139
|
isError: true
|
|
1089
1140
|
};
|
|
1090
1141
|
}
|
|
1091
|
-
const isIOS = app.deviceInfo.title?.toLowerCase().includes(
|
|
1092
|
-
app.deviceInfo.title?.toLowerCase().includes(
|
|
1093
|
-
app.deviceInfo.deviceName?.toLowerCase().includes(
|
|
1094
|
-
app.deviceInfo.description?.toLowerCase().includes(
|
|
1142
|
+
const isIOS = app.deviceInfo.title?.toLowerCase().includes("iphone") ||
|
|
1143
|
+
app.deviceInfo.title?.toLowerCase().includes("ipad") ||
|
|
1144
|
+
app.deviceInfo.deviceName?.toLowerCase().includes("simulator") ||
|
|
1145
|
+
app.deviceInfo.description?.toLowerCase().includes("ios");
|
|
1095
1146
|
// Tap at coordinates
|
|
1096
1147
|
try {
|
|
1097
1148
|
if (isIOS) {
|
|
@@ -1103,15 +1154,17 @@ registerToolWithTelemetry("get_inspector_selection", {
|
|
|
1103
1154
|
}
|
|
1104
1155
|
catch (tapError) {
|
|
1105
1156
|
return {
|
|
1106
|
-
content: [
|
|
1157
|
+
content: [
|
|
1158
|
+
{
|
|
1107
1159
|
type: "text",
|
|
1108
1160
|
text: `Failed to tap at (${x}, ${y}): ${tapError instanceof Error ? tapError.message : String(tapError)}`
|
|
1109
|
-
}
|
|
1161
|
+
}
|
|
1162
|
+
],
|
|
1110
1163
|
isError: true
|
|
1111
1164
|
};
|
|
1112
1165
|
}
|
|
1113
1166
|
// Wait for selection to update
|
|
1114
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
1167
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1115
1168
|
}
|
|
1116
1169
|
// Read the current selection
|
|
1117
1170
|
const result = await getInspectorSelection();
|
|
@@ -1188,7 +1241,9 @@ registerToolWithTelemetry("inspect_at_point", {
|
|
|
1188
1241
|
const parsed = JSON.parse(result.result || "{}");
|
|
1189
1242
|
if (parsed.error) {
|
|
1190
1243
|
const hint = parsed.hint ? `\n\n${parsed.hint}` : "";
|
|
1191
|
-
const alternatives = parsed.alternatives
|
|
1244
|
+
const alternatives = parsed.alternatives
|
|
1245
|
+
? `\n\nAlternatives:\n${parsed.alternatives.map((a) => ` - ${a}`).join("\n")}`
|
|
1246
|
+
: "";
|
|
1192
1247
|
return {
|
|
1193
1248
|
content: [
|
|
1194
1249
|
{
|
|
@@ -1221,18 +1276,9 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1221
1276
|
.optional()
|
|
1222
1277
|
.default(50)
|
|
1223
1278
|
.describe("Maximum number of requests to return (default: 50)"),
|
|
1224
|
-
method: z
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
.describe("Filter by HTTP method (GET, POST, PUT, DELETE, etc.)"),
|
|
1228
|
-
urlPattern: z
|
|
1229
|
-
.string()
|
|
1230
|
-
.optional()
|
|
1231
|
-
.describe("Filter by URL pattern (case-insensitive substring match)"),
|
|
1232
|
-
status: z
|
|
1233
|
-
.number()
|
|
1234
|
-
.optional()
|
|
1235
|
-
.describe("Filter by HTTP status code (e.g., 200, 401, 500)"),
|
|
1279
|
+
method: z.string().optional().describe("Filter by HTTP method (GET, POST, PUT, DELETE, etc.)"),
|
|
1280
|
+
urlPattern: z.string().optional().describe("Filter by URL pattern (case-insensitive substring match)"),
|
|
1281
|
+
status: z.number().optional().describe("Filter by HTTP status code (e.g., 200, 401, 500)"),
|
|
1236
1282
|
format: z
|
|
1237
1283
|
.enum(["text", "tonl"])
|
|
1238
1284
|
.optional()
|
|
@@ -1286,7 +1332,7 @@ registerToolWithTelemetry("get_network_requests", {
|
|
|
1286
1332
|
let gapWarning = "";
|
|
1287
1333
|
if (recentGaps.length > 0) {
|
|
1288
1334
|
const latestGap = recentGaps[recentGaps.length - 1];
|
|
1289
|
-
const gapDuration = latestGap.durationMs ||
|
|
1335
|
+
const gapDuration = latestGap.durationMs || Date.now() - latestGap.disconnectedAt.getTime();
|
|
1290
1336
|
if (latestGap.reconnectedAt) {
|
|
1291
1337
|
const secAgo = Math.round((Date.now() - latestGap.reconnectedAt.getTime()) / 1000);
|
|
1292
1338
|
gapWarning = `\n\n[WARNING] Connection was restored ${secAgo}s ago. Some requests may have been missed during the ${formatDuration(gapDuration)} gap.`;
|
|
@@ -1321,11 +1367,7 @@ registerToolWithTelemetry("search_network", {
|
|
|
1321
1367
|
description: "Search network requests by URL pattern (case-insensitive)",
|
|
1322
1368
|
inputSchema: {
|
|
1323
1369
|
urlPattern: z.string().describe("URL pattern to search for"),
|
|
1324
|
-
maxResults: z
|
|
1325
|
-
.number()
|
|
1326
|
-
.optional()
|
|
1327
|
-
.default(50)
|
|
1328
|
-
.describe("Maximum number of results to return (default: 50)"),
|
|
1370
|
+
maxResults: z.number().optional().default(50).describe("Maximum number of results to return (default: 50)"),
|
|
1329
1371
|
format: z
|
|
1330
1372
|
.enum(["text", "tonl"])
|
|
1331
1373
|
.optional()
|
|
@@ -1372,8 +1414,8 @@ registerToolWithTelemetry("get_request_details", {
|
|
|
1372
1414
|
description: "Get full details of a specific network request including headers, body, and timing. Use get_network_requests first to find the request ID.",
|
|
1373
1415
|
inputSchema: {
|
|
1374
1416
|
requestId: z.string().describe("The request ID to get details for"),
|
|
1375
|
-
maxBodyLength: z
|
|
1376
|
-
.
|
|
1417
|
+
maxBodyLength: z.coerce
|
|
1418
|
+
.number()
|
|
1377
1419
|
.optional()
|
|
1378
1420
|
.default(500)
|
|
1379
1421
|
.describe("Max characters for request body (default: 500, set to 0 for unlimited). Tip: Large POST bodies (file uploads, base64) can be 10KB+."),
|
|
@@ -1500,11 +1542,7 @@ registerToolWithTelemetry("get_bundle_status", {
|
|
|
1500
1542
|
registerToolWithTelemetry("get_bundle_errors", {
|
|
1501
1543
|
description: "Retrieve captured Metro bundling/compilation errors. These are errors that occur during the bundle build process (import resolution, syntax errors, transform errors) that prevent the app from loading. If no errors are captured but Metro is running without connected apps, automatically falls back to screenshot+OCR to capture the error from the device screen.",
|
|
1502
1544
|
inputSchema: {
|
|
1503
|
-
maxErrors: z
|
|
1504
|
-
.number()
|
|
1505
|
-
.optional()
|
|
1506
|
-
.default(10)
|
|
1507
|
-
.describe("Maximum number of errors to return (default: 10)"),
|
|
1545
|
+
maxErrors: z.number().optional().default(10).describe("Maximum number of errors to return (default: 10)"),
|
|
1508
1546
|
platform: z
|
|
1509
1547
|
.enum(["ios", "android"])
|
|
1510
1548
|
.optional()
|
|
@@ -1811,10 +1849,7 @@ registerToolWithTelemetry("android_list_packages", {
|
|
|
1811
1849
|
.string()
|
|
1812
1850
|
.optional()
|
|
1813
1851
|
.describe("Optional device ID. Uses first available device if not specified."),
|
|
1814
|
-
filter: z
|
|
1815
|
-
.string()
|
|
1816
|
-
.optional()
|
|
1817
|
-
.describe("Optional filter to search packages by name (case-insensitive)")
|
|
1852
|
+
filter: z.string().optional().describe("Optional filter to search packages by name (case-insensitive)")
|
|
1818
1853
|
}
|
|
1819
1854
|
}, async ({ deviceId, filter }) => {
|
|
1820
1855
|
const result = await androidListPackages(deviceId, filter);
|
|
@@ -1860,11 +1895,7 @@ registerToolWithTelemetry("android_long_press", {
|
|
|
1860
1895
|
inputSchema: {
|
|
1861
1896
|
x: z.coerce.number().describe("X coordinate in pixels"),
|
|
1862
1897
|
y: z.coerce.number().describe("Y coordinate in pixels"),
|
|
1863
|
-
durationMs: z
|
|
1864
|
-
.number()
|
|
1865
|
-
.optional()
|
|
1866
|
-
.default(1000)
|
|
1867
|
-
.describe("Press duration in milliseconds (default: 1000)"),
|
|
1898
|
+
durationMs: z.number().optional().default(1000).describe("Press duration in milliseconds (default: 1000)"),
|
|
1868
1899
|
deviceId: z
|
|
1869
1900
|
.string()
|
|
1870
1901
|
.optional()
|
|
@@ -1890,11 +1921,7 @@ registerToolWithTelemetry("android_swipe", {
|
|
|
1890
1921
|
startY: z.coerce.number().describe("Starting Y coordinate in pixels"),
|
|
1891
1922
|
endX: z.coerce.number().describe("Ending X coordinate in pixels"),
|
|
1892
1923
|
endY: z.coerce.number().describe("Ending Y coordinate in pixels"),
|
|
1893
|
-
durationMs: z
|
|
1894
|
-
.number()
|
|
1895
|
-
.optional()
|
|
1896
|
-
.default(300)
|
|
1897
|
-
.describe("Swipe duration in milliseconds (default: 300)"),
|
|
1924
|
+
durationMs: z.number().optional().default(300).describe("Swipe duration in milliseconds (default: 300)"),
|
|
1898
1925
|
deviceId: z
|
|
1899
1926
|
.string()
|
|
1900
1927
|
.optional()
|
|
@@ -1938,9 +1965,7 @@ registerToolWithTelemetry("android_input_text", {
|
|
|
1938
1965
|
registerToolWithTelemetry("android_key_event", {
|
|
1939
1966
|
description: `Send a key event to an Android device/emulator. Common keys: ${Object.keys(ANDROID_KEY_EVENTS).join(", ")}`,
|
|
1940
1967
|
inputSchema: {
|
|
1941
|
-
key: z
|
|
1942
|
-
.string()
|
|
1943
|
-
.describe(`Key name (${Object.keys(ANDROID_KEY_EVENTS).join(", ")}) or numeric keycode`),
|
|
1968
|
+
key: z.string().describe(`Key name (${Object.keys(ANDROID_KEY_EVENTS).join(", ")}) or numeric keycode`),
|
|
1944
1969
|
deviceId: z
|
|
1945
1970
|
.string()
|
|
1946
1971
|
.optional()
|
|
@@ -1948,9 +1973,7 @@ registerToolWithTelemetry("android_key_event", {
|
|
|
1948
1973
|
}
|
|
1949
1974
|
}, async ({ key, deviceId }) => {
|
|
1950
1975
|
// Try to parse as number first, otherwise treat as key name
|
|
1951
|
-
const keyCode = /^\d+$/.test(key)
|
|
1952
|
-
? parseInt(key, 10)
|
|
1953
|
-
: key.toUpperCase();
|
|
1976
|
+
const keyCode = /^\d+$/.test(key) ? parseInt(key, 10) : key.toUpperCase();
|
|
1954
1977
|
const result = await androidKeyEvent(keyCode, deviceId);
|
|
1955
1978
|
return {
|
|
1956
1979
|
content: [
|
|
@@ -2044,22 +2067,10 @@ server.registerTool("android_describe_point", {
|
|
|
2044
2067
|
server.registerTool("android_tap_element", {
|
|
2045
2068
|
description: "Tap an element by its text, content-description, or resource-id using uiautomator. TIP: Consider using ocr_screenshot first - it returns ready-to-use tap coordinates for all visible text and works more reliably across different apps.",
|
|
2046
2069
|
inputSchema: {
|
|
2047
|
-
text: z
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
textContains: z
|
|
2052
|
-
.string()
|
|
2053
|
-
.optional()
|
|
2054
|
-
.describe("Partial text match (case-insensitive)"),
|
|
2055
|
-
contentDesc: z
|
|
2056
|
-
.string()
|
|
2057
|
-
.optional()
|
|
2058
|
-
.describe("Exact content-description match"),
|
|
2059
|
-
contentDescContains: z
|
|
2060
|
-
.string()
|
|
2061
|
-
.optional()
|
|
2062
|
-
.describe("Partial content-description match (case-insensitive)"),
|
|
2070
|
+
text: z.string().optional().describe("Exact text match for the element"),
|
|
2071
|
+
textContains: z.string().optional().describe("Partial text match (case-insensitive)"),
|
|
2072
|
+
contentDesc: z.string().optional().describe("Exact content-description match"),
|
|
2073
|
+
contentDescContains: z.string().optional().describe("Partial content-description match (case-insensitive)"),
|
|
2063
2074
|
resourceId: z
|
|
2064
2075
|
.string()
|
|
2065
2076
|
.optional()
|
|
@@ -2098,15 +2109,9 @@ server.registerTool("android_find_element", {
|
|
|
2098
2109
|
description: "Find a UI element on Android screen by text, content description, or resource ID. Returns element details including tap coordinates. Use this to check if an element exists without tapping it. Workflow: 1) wait_for_element, 2) find_element, 3) tap with returned coordinates. Prefer this over screenshots for button taps.",
|
|
2099
2110
|
inputSchema: {
|
|
2100
2111
|
text: z.string().optional().describe("Exact text match for the element"),
|
|
2101
|
-
textContains: z
|
|
2102
|
-
.string()
|
|
2103
|
-
.optional()
|
|
2104
|
-
.describe("Partial text match (case-insensitive)"),
|
|
2112
|
+
textContains: z.string().optional().describe("Partial text match (case-insensitive)"),
|
|
2105
2113
|
contentDesc: z.string().optional().describe("Exact content-description match"),
|
|
2106
|
-
contentDescContains: z
|
|
2107
|
-
.string()
|
|
2108
|
-
.optional()
|
|
2109
|
-
.describe("Partial content-description match (case-insensitive)"),
|
|
2114
|
+
contentDescContains: z.string().optional().describe("Partial content-description match (case-insensitive)"),
|
|
2110
2115
|
resourceId: z
|
|
2111
2116
|
.string()
|
|
2112
2117
|
.optional()
|
|
@@ -2158,15 +2163,9 @@ server.registerTool("android_wait_for_element", {
|
|
|
2158
2163
|
description: "Wait for a UI element to appear on Android screen. Polls the accessibility tree until the element is found or timeout is reached. Use this FIRST after navigation to ensure screen is ready, then use find_element + tap.",
|
|
2159
2164
|
inputSchema: {
|
|
2160
2165
|
text: z.string().optional().describe("Exact text match for the element"),
|
|
2161
|
-
textContains: z
|
|
2162
|
-
.string()
|
|
2163
|
-
.optional()
|
|
2164
|
-
.describe("Partial text match (case-insensitive)"),
|
|
2166
|
+
textContains: z.string().optional().describe("Partial text match (case-insensitive)"),
|
|
2165
2167
|
contentDesc: z.string().optional().describe("Exact content-description match"),
|
|
2166
|
-
contentDescContains: z
|
|
2167
|
-
.string()
|
|
2168
|
-
.optional()
|
|
2169
|
-
.describe("Partial content-description match (case-insensitive)"),
|
|
2168
|
+
contentDescContains: z.string().optional().describe("Partial content-description match (case-insensitive)"),
|
|
2170
2169
|
resourceId: z
|
|
2171
2170
|
.string()
|
|
2172
2171
|
.optional()
|
|
@@ -2456,10 +2455,7 @@ registerToolWithTelemetry("ios_install_app", {
|
|
|
2456
2455
|
description: "Install an app bundle (.app) on an iOS simulator",
|
|
2457
2456
|
inputSchema: {
|
|
2458
2457
|
appPath: z.string().describe("Path to the .app bundle to install"),
|
|
2459
|
-
udid: z
|
|
2460
|
-
.string()
|
|
2461
|
-
.optional()
|
|
2462
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2458
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2463
2459
|
}
|
|
2464
2460
|
}, async ({ appPath, udid }) => {
|
|
2465
2461
|
const result = await iosInstallApp(appPath, udid);
|
|
@@ -2478,10 +2474,7 @@ registerToolWithTelemetry("ios_launch_app", {
|
|
|
2478
2474
|
description: "Launch an app on an iOS simulator by bundle ID",
|
|
2479
2475
|
inputSchema: {
|
|
2480
2476
|
bundleId: z.string().describe("Bundle ID of the app (e.g., com.example.myapp)"),
|
|
2481
|
-
udid: z
|
|
2482
|
-
.string()
|
|
2483
|
-
.optional()
|
|
2484
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2477
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2485
2478
|
}
|
|
2486
2479
|
}, async ({ bundleId, udid }) => {
|
|
2487
2480
|
const result = await iosLaunchApp(bundleId, udid);
|
|
@@ -2500,10 +2493,7 @@ registerToolWithTelemetry("ios_open_url", {
|
|
|
2500
2493
|
description: "Open a URL in the iOS simulator (opens in default handler or Safari)",
|
|
2501
2494
|
inputSchema: {
|
|
2502
2495
|
url: z.string().describe("URL to open (e.g., https://example.com or myapp://path)"),
|
|
2503
|
-
udid: z
|
|
2504
|
-
.string()
|
|
2505
|
-
.optional()
|
|
2506
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2496
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2507
2497
|
}
|
|
2508
2498
|
}, async ({ url, udid }) => {
|
|
2509
2499
|
const result = await iosOpenUrl(url, udid);
|
|
@@ -2522,10 +2512,7 @@ registerToolWithTelemetry("ios_terminate_app", {
|
|
|
2522
2512
|
description: "Terminate a running app on an iOS simulator",
|
|
2523
2513
|
inputSchema: {
|
|
2524
2514
|
bundleId: z.string().describe("Bundle ID of the app to terminate"),
|
|
2525
|
-
udid: z
|
|
2526
|
-
.string()
|
|
2527
|
-
.optional()
|
|
2528
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2515
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2529
2516
|
}
|
|
2530
2517
|
}, async ({ bundleId, udid }) => {
|
|
2531
2518
|
const result = await iosTerminateApp(bundleId, udid);
|
|
@@ -2567,14 +2554,8 @@ server.registerTool("ios_tap", {
|
|
|
2567
2554
|
inputSchema: {
|
|
2568
2555
|
x: z.coerce.number().describe("X coordinate in pixels"),
|
|
2569
2556
|
y: z.coerce.number().describe("Y coordinate in pixels"),
|
|
2570
|
-
duration: z
|
|
2571
|
-
|
|
2572
|
-
.optional()
|
|
2573
|
-
.describe("Optional tap duration in seconds (for long press)"),
|
|
2574
|
-
udid: z
|
|
2575
|
-
.string()
|
|
2576
|
-
.optional()
|
|
2577
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2557
|
+
duration: z.number().optional().describe("Optional tap duration in seconds (for long press)"),
|
|
2558
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2578
2559
|
}
|
|
2579
2560
|
}, async ({ x, y, duration, udid }) => {
|
|
2580
2561
|
const result = await iosTap(x, y, { duration, udid });
|
|
@@ -2592,10 +2573,7 @@ server.registerTool("ios_tap", {
|
|
|
2592
2573
|
server.registerTool("ios_tap_element", {
|
|
2593
2574
|
description: "Tap an element by its accessibility label. Requires IDB (brew install idb-companion). TIP: Consider using ocr_screenshot first - it returns ready-to-use tap coordinates for all visible text and works without requiring accessibility labels.",
|
|
2594
2575
|
inputSchema: {
|
|
2595
|
-
label: z
|
|
2596
|
-
.string()
|
|
2597
|
-
.optional()
|
|
2598
|
-
.describe("Exact accessibility label to match (e.g., 'Home', 'Settings')"),
|
|
2576
|
+
label: z.string().optional().describe("Exact accessibility label to match (e.g., 'Home', 'Settings')"),
|
|
2599
2577
|
labelContains: z
|
|
2600
2578
|
.string()
|
|
2601
2579
|
.optional()
|
|
@@ -2604,14 +2582,8 @@ server.registerTool("ios_tap_element", {
|
|
|
2604
2582
|
.number()
|
|
2605
2583
|
.optional()
|
|
2606
2584
|
.describe("If multiple elements match, tap the nth one (0-indexed, default: 0)"),
|
|
2607
|
-
duration: z
|
|
2608
|
-
|
|
2609
|
-
.optional()
|
|
2610
|
-
.describe("Optional tap duration in seconds (for long press)"),
|
|
2611
|
-
udid: z
|
|
2612
|
-
.string()
|
|
2613
|
-
.optional()
|
|
2614
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2585
|
+
duration: z.number().optional().describe("Optional tap duration in seconds (for long press)"),
|
|
2586
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2615
2587
|
}
|
|
2616
2588
|
}, async ({ label, labelContains, index, duration, udid }) => {
|
|
2617
2589
|
const result = await iosTapElement({ label, labelContains, index, duration, udid });
|
|
@@ -2635,10 +2607,7 @@ server.registerTool("ios_swipe", {
|
|
|
2635
2607
|
endY: z.coerce.number().describe("Ending Y coordinate in pixels"),
|
|
2636
2608
|
duration: z.coerce.number().optional().describe("Optional swipe duration in seconds"),
|
|
2637
2609
|
delta: z.coerce.number().optional().describe("Optional delta between touch events (step size)"),
|
|
2638
|
-
udid: z
|
|
2639
|
-
.string()
|
|
2640
|
-
.optional()
|
|
2641
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2610
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2642
2611
|
}
|
|
2643
2612
|
}, async ({ startX, startY, endX, endY, duration, delta, udid }) => {
|
|
2644
2613
|
const result = await iosSwipe(startX, startY, endX, endY, { duration, delta, udid });
|
|
@@ -2657,10 +2626,7 @@ server.registerTool("ios_input_text", {
|
|
|
2657
2626
|
description: "Type text into the active input field on an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
|
|
2658
2627
|
inputSchema: {
|
|
2659
2628
|
text: z.string().describe("Text to type into the active input field"),
|
|
2660
|
-
udid: z
|
|
2661
|
-
.string()
|
|
2662
|
-
.optional()
|
|
2663
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2629
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2664
2630
|
}
|
|
2665
2631
|
}, async ({ text, udid }) => {
|
|
2666
2632
|
const result = await iosInputText(text, udid);
|
|
@@ -2682,10 +2648,7 @@ server.registerTool("ios_button", {
|
|
|
2682
2648
|
.enum(IOS_BUTTON_TYPES)
|
|
2683
2649
|
.describe("Hardware button to press: HOME, LOCK, SIDE_BUTTON, SIRI, or APPLE_PAY"),
|
|
2684
2650
|
duration: z.coerce.number().optional().describe("Optional button press duration in seconds"),
|
|
2685
|
-
udid: z
|
|
2686
|
-
.string()
|
|
2687
|
-
.optional()
|
|
2688
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2651
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2689
2652
|
}
|
|
2690
2653
|
}, async ({ button, duration, udid }) => {
|
|
2691
2654
|
const result = await iosButton(button, { duration, udid });
|
|
@@ -2705,10 +2668,7 @@ server.registerTool("ios_key_event", {
|
|
|
2705
2668
|
inputSchema: {
|
|
2706
2669
|
keycode: z.coerce.number().describe("iOS keycode to send"),
|
|
2707
2670
|
duration: z.coerce.number().optional().describe("Optional key press duration in seconds"),
|
|
2708
|
-
udid: z
|
|
2709
|
-
.string()
|
|
2710
|
-
.optional()
|
|
2711
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2671
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2712
2672
|
}
|
|
2713
2673
|
}, async ({ keycode, duration, udid }) => {
|
|
2714
2674
|
const result = await iosKeyEvent(keycode, { duration, udid });
|
|
@@ -2727,10 +2687,7 @@ server.registerTool("ios_key_sequence", {
|
|
|
2727
2687
|
description: "Send a sequence of key events to an iOS simulator. Requires IDB to be installed (brew install idb-companion).",
|
|
2728
2688
|
inputSchema: {
|
|
2729
2689
|
keycodes: z.array(z.coerce.number()).describe("Array of iOS keycodes to send in sequence"),
|
|
2730
|
-
udid: z
|
|
2731
|
-
.string()
|
|
2732
|
-
.optional()
|
|
2733
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2690
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2734
2691
|
}
|
|
2735
2692
|
}, async ({ keycodes, udid }) => {
|
|
2736
2693
|
const result = await iosKeySequence(keycodes, udid);
|
|
@@ -2748,10 +2705,7 @@ server.registerTool("ios_key_sequence", {
|
|
|
2748
2705
|
server.registerTool("ios_describe_all", {
|
|
2749
2706
|
description: "Get accessibility information for the entire iOS simulator screen. Returns a nested tree of UI elements with labels, values, and frames. Requires IDB to be installed (brew install idb-companion).",
|
|
2750
2707
|
inputSchema: {
|
|
2751
|
-
udid: z
|
|
2752
|
-
.string()
|
|
2753
|
-
.optional()
|
|
2754
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2708
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2755
2709
|
}
|
|
2756
2710
|
}, async ({ udid }) => {
|
|
2757
2711
|
const result = await iosDescribeAll(udid);
|
|
@@ -2771,10 +2725,7 @@ server.registerTool("ios_describe_point", {
|
|
|
2771
2725
|
inputSchema: {
|
|
2772
2726
|
x: z.coerce.number().describe("X coordinate in pixels"),
|
|
2773
2727
|
y: z.coerce.number().describe("Y coordinate in pixels"),
|
|
2774
|
-
udid: z
|
|
2775
|
-
.string()
|
|
2776
|
-
.optional()
|
|
2777
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2728
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2778
2729
|
}
|
|
2779
2730
|
}, async ({ x, y, udid }) => {
|
|
2780
2731
|
const result = await iosDescribePoint(x, y, udid);
|
|
@@ -2793,27 +2744,15 @@ server.registerTool("ios_find_element", {
|
|
|
2793
2744
|
description: "Find a UI element on iOS simulator by accessibility label or value. Returns element details including tap coordinates. Requires IDB (brew install idb-companion). Workflow: 1) wait_for_element, 2) find_element, 3) tap with returned coordinates. Prefer this over screenshots for button taps.",
|
|
2794
2745
|
inputSchema: {
|
|
2795
2746
|
label: z.string().optional().describe("Exact accessibility label match"),
|
|
2796
|
-
labelContains: z
|
|
2797
|
-
.string()
|
|
2798
|
-
.optional()
|
|
2799
|
-
.describe("Partial label match (case-insensitive)"),
|
|
2747
|
+
labelContains: z.string().optional().describe("Partial label match (case-insensitive)"),
|
|
2800
2748
|
value: z.string().optional().describe("Exact accessibility value match"),
|
|
2801
|
-
valueContains: z
|
|
2802
|
-
|
|
2803
|
-
.optional()
|
|
2804
|
-
.describe("Partial value match (case-insensitive)"),
|
|
2805
|
-
type: z
|
|
2806
|
-
.string()
|
|
2807
|
-
.optional()
|
|
2808
|
-
.describe("Element type to match (e.g., 'Button', 'TextField')"),
|
|
2749
|
+
valueContains: z.string().optional().describe("Partial value match (case-insensitive)"),
|
|
2750
|
+
type: z.string().optional().describe("Element type to match (e.g., 'Button', 'TextField')"),
|
|
2809
2751
|
index: z
|
|
2810
2752
|
.number()
|
|
2811
2753
|
.optional()
|
|
2812
2754
|
.describe("If multiple elements match, select the nth one (0-indexed, default: 0)"),
|
|
2813
|
-
udid: z
|
|
2814
|
-
.string()
|
|
2815
|
-
.optional()
|
|
2816
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2755
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2817
2756
|
}
|
|
2818
2757
|
}, async ({ label, labelContains, value, valueContains, type, index, udid }) => {
|
|
2819
2758
|
const result = await iosFindElement({ label, labelContains, value, valueContains, type, index }, udid);
|
|
@@ -2852,19 +2791,10 @@ server.registerTool("ios_wait_for_element", {
|
|
|
2852
2791
|
description: "Wait for a UI element to appear on iOS simulator. Polls until found or timeout. Requires IDB (brew install idb-companion). Use this FIRST after navigation to ensure screen is ready, then use find_element + tap.",
|
|
2853
2792
|
inputSchema: {
|
|
2854
2793
|
label: z.string().optional().describe("Exact accessibility label match"),
|
|
2855
|
-
labelContains: z
|
|
2856
|
-
.string()
|
|
2857
|
-
.optional()
|
|
2858
|
-
.describe("Partial label match (case-insensitive)"),
|
|
2794
|
+
labelContains: z.string().optional().describe("Partial label match (case-insensitive)"),
|
|
2859
2795
|
value: z.string().optional().describe("Exact accessibility value match"),
|
|
2860
|
-
valueContains: z
|
|
2861
|
-
|
|
2862
|
-
.optional()
|
|
2863
|
-
.describe("Partial value match (case-insensitive)"),
|
|
2864
|
-
type: z
|
|
2865
|
-
.string()
|
|
2866
|
-
.optional()
|
|
2867
|
-
.describe("Element type to match (e.g., 'Button', 'TextField')"),
|
|
2796
|
+
valueContains: z.string().optional().describe("Partial value match (case-insensitive)"),
|
|
2797
|
+
type: z.string().optional().describe("Element type to match (e.g., 'Button', 'TextField')"),
|
|
2868
2798
|
index: z
|
|
2869
2799
|
.number()
|
|
2870
2800
|
.optional()
|
|
@@ -2879,10 +2809,7 @@ server.registerTool("ios_wait_for_element", {
|
|
|
2879
2809
|
.optional()
|
|
2880
2810
|
.default(500)
|
|
2881
2811
|
.describe("Time between polls in milliseconds (default: 500)"),
|
|
2882
|
-
udid: z
|
|
2883
|
-
.string()
|
|
2884
|
-
.optional()
|
|
2885
|
-
.describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2812
|
+
udid: z.string().optional().describe("Optional simulator UDID. Uses booted simulator if not specified.")
|
|
2886
2813
|
}
|
|
2887
2814
|
}, async ({ label, labelContains, value, valueContains, type, index, timeoutMs, pollIntervalMs, udid }) => {
|
|
2888
2815
|
const result = await iosWaitForElement({
|