mobile-debug-mcp 0.31.0 → 0.32.1

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 (109) hide show
  1. package/README.md +1 -0
  2. package/dist/interact/android.js +13 -8
  3. package/dist/interact/index.js +33 -22
  4. package/dist/interact/ios.js +5 -2
  5. package/dist/manage/android.js +20 -10
  6. package/dist/manage/ios.js +19 -10
  7. package/dist/observe/android-tree-bridge.js +192 -0
  8. package/dist/observe/android.js +53 -14
  9. package/dist/observe/index.js +22 -5
  10. package/dist/observe/ios.js +50 -29
  11. package/dist/observe/performance-metrics.js +124 -0
  12. package/dist/observe/snapshot-metadata.js +94 -15
  13. package/dist/server/common.js +14 -7
  14. package/dist/server/journey-schema.js +60 -0
  15. package/dist/server/tool-definitions.js +59 -6
  16. package/dist/server/tool-handlers.js +381 -56
  17. package/dist/server-core.js +1 -1
  18. package/dist/utils/exec.js +18 -5
  19. package/dist/utils/ios/utils.js +5 -2
  20. package/dist/utils/operation-budget.js +25 -0
  21. package/dist/utils/resolve-device.js +12 -1
  22. package/dist/utils/timing.js +19 -0
  23. package/dist/utils/ui/index.js +2 -0
  24. package/docs/CHANGELOG.md +6 -0
  25. package/docs/ROADMAP.md +9 -0
  26. package/docs/benchmarks/interaction-latency/README.md +26 -0
  27. package/docs/benchmarks/interaction-latency/final.json +1810 -0
  28. package/docs/benchmarks/interaction-latency/final.md +17 -0
  29. package/docs/benchmarks/interaction-latency/ios-validation.md +11 -0
  30. package/docs/benchmarks/ui-tree-performance/README.md +51 -0
  31. package/docs/benchmarks/ui-tree-performance/baseline.json +59546 -0
  32. package/docs/benchmarks/ui-tree-performance/bridge-probe.json +28 -0
  33. package/docs/benchmarks/ui-tree-performance/candidate.json +51691 -0
  34. package/docs/benchmarks/ui-tree-performance/comparison-attempt-1.json +110679 -0
  35. package/docs/benchmarks/ui-tree-performance/comparison.json +111230 -0
  36. package/docs/benchmarks/ui-tree-performance/comparison.md +41 -0
  37. package/docs/benchmarks/ui-tree-performance/initial-baseline.json +59560 -0
  38. package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +301 -0
  39. package/docs/specs/019-ui-tree-observation-performance/spec.md +148 -0
  40. package/docs/tools/interact.md +30 -0
  41. package/docs/tools/observe.md +1 -0
  42. package/package.json +1 -1
  43. package/skills/mobile-debug-tools-usage/SKILL.md +74 -0
  44. package/src/interact/android.ts +13 -8
  45. package/src/interact/index.ts +32 -23
  46. package/src/interact/ios.ts +5 -2
  47. package/src/manage/android.ts +19 -10
  48. package/src/manage/ios.ts +18 -10
  49. package/src/observe/android-tree-bridge.ts +170 -0
  50. package/src/observe/android.ts +48 -17
  51. package/src/observe/index.ts +21 -5
  52. package/src/observe/ios.ts +49 -36
  53. package/src/observe/performance-metrics.ts +125 -0
  54. package/src/observe/snapshot-metadata.ts +94 -15
  55. package/src/server/common.ts +13 -6
  56. package/src/server/journey-schema.ts +63 -0
  57. package/src/server/tool-definitions.ts +60 -7
  58. package/src/server/tool-handlers.ts +349 -54
  59. package/src/server-core.ts +1 -1
  60. package/src/types.ts +31 -2
  61. package/src/utils/exec.ts +13 -5
  62. package/src/utils/ios/utils.ts +5 -2
  63. package/src/utils/operation-budget.ts +22 -0
  64. package/src/utils/resolve-device.ts +13 -1
  65. package/src/utils/timing.ts +15 -0
  66. package/src/utils/ui/index.ts +2 -0
  67. package/test/device/automated/latency/benchmark.ts +136 -0
  68. package/test/device/automated/latency/report.ts +6 -0
  69. package/test/device/automated/ui-tree-performance/benchmark.ts +401 -0
  70. package/test/device/automated/ui-tree-performance/bridge-probe.ts +183 -0
  71. package/test/fixtures/latency-journey-app/AndroidManifest.xml +7 -0
  72. package/test/fixtures/latency-journey-app/README.md +28 -0
  73. package/test/fixtures/latency-journey-app/build.ts +26 -0
  74. package/test/fixtures/latency-journey-app/src/MainActivity.java +104 -0
  75. package/test/fixtures/ui-tree-bridge/AndroidManifest.xml +6 -0
  76. package/test/fixtures/ui-tree-bridge/build.ts +34 -0
  77. package/test/fixtures/ui-tree-bridge/src/TreeInstrumentation.java +119 -0
  78. package/test/unit/manage/restart_budget.test.ts +37 -0
  79. package/test/unit/observe/android_tree_acquisition.test.ts +70 -0
  80. package/test/unit/observe/android_tree_bridge_cleanup.test.ts +76 -0
  81. package/test/unit/observe/capture_debug_snapshot.test.ts +6 -0
  82. package/test/unit/observe/ios_tree_acquisition.test.ts +77 -0
  83. package/test/unit/observe/performance_metrics.test.ts +40 -0
  84. package/test/unit/observe/snapshot_metadata.test.ts +20 -1
  85. package/test/unit/observe/state_delta.test.ts +15 -0
  86. package/test/unit/server/benchmark_report.test.ts +4 -0
  87. package/test/unit/server/contract.test.ts +1 -1
  88. package/test/unit/server/interaction_modes.test.ts +60 -0
  89. package/test/unit/server/operation_budget.test.ts +45 -0
  90. package/test/unit/server/response_shapes.test.ts +3 -3
  91. package/test/unit/server/run_journey.test.ts +120 -0
  92. package/test/unit/server/tap_selector.test.ts +68 -0
  93. /package/docs/{specs-deltas/001-state-verification.md → specs/001-state-verification/spec.md} +0 -0
  94. /package/docs/{specs-deltas/002-richer-element-identity → specs/002-richer-element-identity/spec.md} +0 -0
  95. /package/docs/{specs-deltas/003-wait-and-synchronization-reliability.md → specs/003-wait-and-synchronization-reliability/spec.md} +0 -0
  96. /package/docs/{specs-deltas/004-action-verification-routing.md → specs/004-action-verification-routing/spec.md} +0 -0
  97. /package/docs/{specs-deltas/005-unified-action-execution-and-verification-model.md → specs/005-unified-action-execution-and-verification-model/spec.md} +0 -0
  98. /package/docs/{specs-deltas/006-runtime-action-instrumentation-and-binding-layer.md → specs/006-runtime-action-instrumentation-and-binding-layer/spec.md} +0 -0
  99. /package/docs/{specs-deltas/007-actionability-resolution-and-executable-target-selection.md → specs/007-actionability-resolution-and-executable-target-selection/spec.md} +0 -0
  100. /package/docs/{specs-deltas/008-adjustable-control-support-and-semantic-value-manipulation.md → specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md} +0 -0
  101. /package/docs/{specs-deltas/009-semantic-control-modeling-for-custom-and-composite-controls.md → specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md} +0 -0
  102. /package/docs/{specs-deltas/010-verification-stabilization-and-temporal-convergence.md → specs/010-verification-stabilization-and-temporal-convergence/spec.md} +0 -0
  103. /package/docs/{specs-deltas/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows.md → specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md} +0 -0
  104. /package/docs/{specs-deltas/011.1-recovery-contract-types-and-runtime-wiring-spec.md → specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md} +0 -0
  105. /package/docs/{specs-deltas/012-action-trace-and-xecution-observability.md → specs/012-action-trace-and-xecution-observability/spec.md} +0 -0
  106. /package/docs/{specs-deltas/013-wait-and-synchronization-reliability.md → specs/013-wait-and-synchronization-reliability/spec.md} +0 -0
  107. /package/docs/{specs-deltas/014-actionability-resolution.md → specs/014-actionability-resolution/spec.md} +0 -0
  108. /package/docs/{specs-deltas/015-adjustable-control-support.md → specs/015-adjustable-control-support/spec.md} +0 -0
  109. /package/docs/{specs-deltas/016-environment-tools.md → specs/016-environment-tools/spec.md} +0 -0
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ const selector = z.object({
3
+ text: z.string().min(1).optional(), resource_id: z.string().min(1).optional(),
4
+ accessibility_id: z.string().min(1).optional(), contains: z.boolean().optional()
5
+ }).strict().refine(value => !!(value.text || value.resource_id || value.accessibility_id), 'Selector requires a target');
6
+ const verificationMode = z.enum(['none', 'light', 'full']);
7
+ const timeoutMs = z.number().int().min(100).max(60000).optional();
8
+ const pollIntervalMs = z.number().int().min(50).max(1000).optional();
9
+ const waitFor = z.object({
10
+ condition: z.enum(['exists', 'visible', 'clickable']).optional(),
11
+ timeoutMs: z.number().int().min(100).max(10000).optional(), pollIntervalMs,
12
+ match: z.object({ index: z.number().int().nonnegative().optional() }).strict().optional()
13
+ }).strict();
14
+ const target = { selector: selector.optional(), elementId: z.string().min(1).optional() };
15
+ const exactlyOneTarget = (value) => Number(!!value.selector) + Number(!!value.elementId) === 1;
16
+ const assertion = z.union([
17
+ z.object({ kind: z.literal('element_visible'), selector }).strict(),
18
+ z.object({ kind: z.literal('element_absent'), selector }).strict(),
19
+ z.object({ kind: z.literal('screen_fingerprint'), fingerprint: z.string().min(1) }).strict(),
20
+ z.object({ kind: z.literal('state_equals'), property: z.string().min(1), expected: z.union([z.boolean(), z.number().finite(), z.string()]), ...target }).strict().refine(exactlyOneTarget)
21
+ ]);
22
+ const id = z.string().min(1);
23
+ const step = z.union([
24
+ z.object({ id, type: z.literal('start_app'), appId: z.string().min(1), verificationMode: verificationMode.optional() }).strict(),
25
+ z.object({ id, type: z.literal('tap'), ...target, waitFor: waitFor.optional(), verificationMode: verificationMode.optional() }).strict().refine(exactlyOneTarget),
26
+ z.object({ id, type: z.literal('wait'), selector, condition: z.enum(['exists', 'not_exists', 'visible', 'clickable']).optional(), timeoutMs, pollIntervalMs }).strict(),
27
+ z.object({ id, type: z.literal('assert'), assertion, timeoutMs, pollIntervalMs }).strict()
28
+ ]);
29
+ export const tapElementControlsSchema = z.object({
30
+ elementId: z.string().min(1).optional(), selector: selector.optional(), waitFor: waitFor.optional()
31
+ }).strict().refine(value => Number(!!value.elementId) + Number(!!value.selector) === 1 && (!value.elementId || !value.waitFor));
32
+ export const journeySchema = z.object({
33
+ platform: z.enum(['android', 'ios']), deviceId: z.string().min(1).optional(),
34
+ responseMode: z.enum(['compact', 'debug']).default('compact'), captureOnFailure: z.boolean().default(false),
35
+ defaults: z.object({ verificationMode: verificationMode.optional(), actionTimeoutMs: z.number().int().finite().optional(), verificationTimeoutMs: z.number().int().finite().optional() }).strict().optional(),
36
+ steps: z.array(step).min(1).max(50)
37
+ }).strict().refine(value => new Set(value.steps.map(step => step.id)).size === value.steps.length, 'Step IDs must be unique');
38
+ const selectorJson = { type: 'object', additionalProperties: false, properties: {
39
+ text: { type: 'string', minLength: 1 }, resource_id: { type: 'string', minLength: 1 }, accessibility_id: { type: 'string', minLength: 1 }, contains: { type: 'boolean' }
40
+ }, anyOf: ['text', 'resource_id', 'accessibility_id'].map(key => ({ required: [key] })) };
41
+ const targetJson = { selector: selectorJson, elementId: { type: 'string', minLength: 1 } };
42
+ const oneTargetJson = [{ required: ['selector'], not: { required: ['elementId'] } }, { required: ['elementId'], not: { required: ['selector'] } }];
43
+ const intervalJson = { timeoutMs: { type: 'integer', minimum: 100, maximum: 60000 }, pollIntervalMs: { type: 'integer', minimum: 50, maximum: 1000 } };
44
+ const modeJson = { type: 'string', enum: ['none', 'light', 'full'] };
45
+ const objectJson = (properties, required, extra = {}) => ({ type: 'object', additionalProperties: false, properties, required, ...extra });
46
+ export const journeyStepsJson = {
47
+ type: 'array', minItems: 1, maxItems: 50, items: { oneOf: [
48
+ objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'start_app' }, appId: { type: 'string', minLength: 1 }, verificationMode: modeJson }, ['id', 'type', 'appId']),
49
+ objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'tap' }, ...targetJson, verificationMode: modeJson,
50
+ waitFor: objectJson({ ...intervalJson, timeoutMs: { type: 'integer', minimum: 100, maximum: 10000 }, condition: { enum: ['exists', 'visible', 'clickable'] }, match: objectJson({ index: { type: 'integer', minimum: 0 } }, []) }, [])
51
+ }, ['id', 'type'], { oneOf: oneTargetJson }),
52
+ objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'wait' }, selector: selectorJson, condition: { enum: ['exists', 'not_exists', 'visible', 'clickable'] }, ...intervalJson }, ['id', 'type', 'selector']),
53
+ objectJson({ id: { type: 'string', minLength: 1 }, type: { const: 'assert' }, ...intervalJson, assertion: { oneOf: [
54
+ objectJson({ kind: { const: 'element_visible' }, selector: selectorJson }, ['kind', 'selector']),
55
+ objectJson({ kind: { const: 'element_absent' }, selector: selectorJson }, ['kind', 'selector']),
56
+ objectJson({ kind: { const: 'screen_fingerprint' }, fingerprint: { type: 'string', minLength: 1 } }, ['kind', 'fingerprint']),
57
+ objectJson({ kind: { const: 'state_equals' }, ...targetJson, property: { type: 'string', minLength: 1 }, expected: { type: ['boolean', 'number', 'string'] } }, ['kind', 'property', 'expected'], { oneOf: oneTargetJson })
58
+ ] } }, ['id', 'type', 'assertion'])
59
+ ] }
60
+ };
@@ -1,3 +1,4 @@
1
+ import { journeyStepsJson } from './journey-schema.js';
1
2
  export const toolDefinitions = [
2
3
  {
3
4
  name: 'start_app',
@@ -47,7 +48,11 @@ Failure Handling:
47
48
  deviceId: {
48
49
  type: 'string',
49
50
  description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.'
50
- }
51
+ },
52
+ verificationMode: { type: 'string', enum: ['none', 'light', 'full'], default: 'full' },
53
+ responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'debug' },
54
+ actionTimeoutMs: { type: 'number', minimum: 100, maximum: 30000 },
55
+ verificationTimeoutMs: { type: 'number', minimum: 100, maximum: 60000 }
51
56
  },
52
57
  required: ['platform', 'appId']
53
58
  }
@@ -250,6 +255,7 @@ Failure Handling:
250
255
  properties: {
251
256
  reason: { type: 'string', description: 'Optional reason for snapshot' },
252
257
  includeLogs: { type: 'boolean', description: 'Whether to include logs', default: true },
258
+ includeScreenshot: { type: 'boolean', description: 'Whether to collect screenshot bytes', default: true },
253
259
  logLines: { type: 'number', description: 'Maximum number of log lines to include', default: 200 },
254
260
  platform: { type: 'string', enum: ['android', 'ios'], description: 'Optional platform override' },
255
261
  appId: { type: 'string', description: 'Optional appId to scope logs (package/bundle id)' },
@@ -307,7 +313,9 @@ Failure Handling:
307
313
  deviceId: {
308
314
  type: 'string',
309
315
  description: 'Device Serial (Android) or UDID (iOS). Defaults to connected/booted device.'
310
- }
316
+ },
317
+ responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'debug' },
318
+ sinceSnapshotRevision: { type: 'number', minimum: 1 }
311
319
  },
312
320
  required: ['platform']
313
321
  }
@@ -729,7 +737,11 @@ Failure Handling:
729
737
  deviceId: {
730
738
  type: 'string',
731
739
  description: 'Device Serial/UDID. Defaults to connected/booted device.'
732
- }
740
+ },
741
+ verificationMode: { type: 'string', enum: ['none', 'light', 'full'], default: 'full' },
742
+ responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'debug' },
743
+ actionTimeoutMs: { type: 'number', minimum: 100, maximum: 30000 },
744
+ verificationTimeoutMs: { type: 'number', minimum: 100, maximum: 60000 }
733
745
  },
734
746
  required: ['x', 'y']
735
747
  }
@@ -783,9 +795,14 @@ This tool reports execution success only. Verification must be done with a separ
783
795
  elementId: {
784
796
  type: 'string',
785
797
  description: 'A unique element identifier returned by wait_for_ui'
786
- }
787
- },
788
- required: ['elementId']
798
+ },
799
+ selector: { type: 'object', properties: { text: { type: 'string' }, resource_id: { type: 'string' }, accessibility_id: { type: 'string' }, contains: { type: 'boolean' } } },
800
+ waitFor: { type: 'object', properties: { condition: { type: 'string', enum: ['exists', 'visible', 'clickable'] }, timeoutMs: { type: 'number', minimum: 100, maximum: 10000 }, pollIntervalMs: { type: 'number', minimum: 50, maximum: 1000 }, match: { type: 'object', properties: { index: { type: 'number' } } } } },
801
+ platform: { type: 'string', enum: ['android', 'ios'] },
802
+ deviceId: { type: 'string' },
803
+ verificationMode: { type: 'string', enum: ['none', 'light', 'full'], default: 'full' },
804
+ responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'debug' }
805
+ }
789
806
  }
790
807
  },
791
808
  {
@@ -1062,6 +1079,20 @@ BEHAVIOUR after outcome:
1062
1079
  required: ['uiChanged']
1063
1080
  }
1064
1081
  },
1082
+ {
1083
+ name: 'run_journey',
1084
+ description: 'Run an ordered, bounded sequence of start_app, tap, wait, and deterministic assertion steps against one device. Stops after the first failed step.',
1085
+ inputSchema: {
1086
+ type: 'object',
1087
+ properties: {
1088
+ platform: { type: 'string', enum: ['android', 'ios'] },
1089
+ deviceId: { type: 'string' },
1090
+ responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'compact' },
1091
+ steps: { type: 'array', minItems: 1, maxItems: 50, items: { type: 'object', properties: { id: { type: 'string' }, type: { type: 'string', enum: ['start_app', 'tap', 'wait', 'assert'] } }, required: ['id', 'type'] } }
1092
+ },
1093
+ required: ['platform', 'steps']
1094
+ }
1095
+ },
1065
1096
  {
1066
1097
  name: 'get_network_activity',
1067
1098
  description: `Returns structured network events captured from platform logs since the last action.
@@ -1095,3 +1126,25 @@ Returns { requests: [], count: 0 } when no credible network signals are found.`,
1095
1126
  }
1096
1127
  }
1097
1128
  ];
1129
+ // Keep the additive execution controls consistent across all action tools.
1130
+ for (const tool of toolDefinitions) {
1131
+ if (['start_app', 'restart_app', 'tap', 'tap_element', 'swipe', 'scroll_to_element', 'type_text', 'press_back'].includes(tool.name)) {
1132
+ Object.assign(tool.inputSchema.properties, {
1133
+ verificationMode: { type: 'string', enum: ['none', 'light', 'full'], default: 'full' },
1134
+ responseMode: { type: 'string', enum: ['compact', 'debug'], default: 'debug' },
1135
+ actionTimeoutMs: { type: 'integer', description: 'Dispatch budget in milliseconds, clamped to 100–30000.' },
1136
+ verificationTimeoutMs: { type: 'integer', description: 'Light observation budget in milliseconds, clamped to 100–60000.' }
1137
+ });
1138
+ }
1139
+ if (tool.name === 'tap_element')
1140
+ Object.assign(tool.inputSchema, { oneOf: [{ required: ['elementId'], not: { required: ['selector'] } }, { required: ['selector'], not: { required: ['elementId'] } }] });
1141
+ if (tool.name === 'run_journey')
1142
+ Object.assign(tool.inputSchema.properties, {
1143
+ steps: journeyStepsJson,
1144
+ captureOnFailure: { type: 'boolean', default: false },
1145
+ defaults: { type: 'object', additionalProperties: false, properties: {
1146
+ verificationMode: { type: 'string', enum: ['none', 'light', 'full'] },
1147
+ actionTimeoutMs: { type: 'integer' }, verificationTimeoutMs: { type: 'integer' }
1148
+ } }
1149
+ });
1150
+ }