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