@0xhayd3n/fling 0.6.0
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/adb.d.ts +18 -0
- package/dist/adb.js +109 -0
- package/dist/adb.js.map +1 -0
- package/dist/apkFinder.d.ts +19 -0
- package/dist/apkFinder.js +113 -0
- package/dist/apkFinder.js.map +1 -0
- package/dist/apkResolver.d.ts +14 -0
- package/dist/apkResolver.js +55 -0
- package/dist/apkResolver.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +113 -0
- package/dist/config.js.map +1 -0
- package/dist/devices.d.ts +24 -0
- package/dist/devices.js +143 -0
- package/dist/devices.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/featureFlags.d.ts +6 -0
- package/dist/featureFlags.js +11 -0
- package/dist/featureFlags.js.map +1 -0
- package/dist/gradle.d.ts +39 -0
- package/dist/gradle.js +129 -0
- package/dist/gradle.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/mdns.d.ts +19 -0
- package/dist/mdns.js +68 -0
- package/dist/mdns.js.map +1 -0
- package/dist/pairing.d.ts +52 -0
- package/dist/pairing.js +212 -0
- package/dist/pairing.js.map +1 -0
- package/dist/qrText.d.ts +5 -0
- package/dist/qrText.js +8 -0
- package/dist/qrText.js.map +1 -0
- package/dist/schemas.d.ts +31 -0
- package/dist/schemas.js +26 -0
- package/dist/schemas.js.map +1 -0
- package/dist/shellFraming.d.ts +28 -0
- package/dist/shellFraming.js +43 -0
- package/dist/shellFraming.js.map +1 -0
- package/dist/shellPool.d.ts +54 -0
- package/dist/shellPool.js +181 -0
- package/dist/shellPool.js.map +1 -0
- package/dist/toolResult.d.ts +10 -0
- package/dist/toolResult.js +11 -0
- package/dist/toolResult.js.map +1 -0
- package/dist/tools/build-app.d.ts +2 -0
- package/dist/tools/build-app.js +74 -0
- package/dist/tools/build-app.js.map +1 -0
- package/dist/tools/deploy-and-run.d.ts +2 -0
- package/dist/tools/deploy-and-run.js +195 -0
- package/dist/tools/deploy-and-run.js.map +1 -0
- package/dist/tools/device-state.d.ts +11 -0
- package/dist/tools/device-state.js +140 -0
- package/dist/tools/device-state.js.map +1 -0
- package/dist/tools/dismiss-dialog.d.ts +15 -0
- package/dist/tools/dismiss-dialog.js +89 -0
- package/dist/tools/dismiss-dialog.js.map +1 -0
- package/dist/tools/dump-ui.d.ts +4 -0
- package/dist/tools/dump-ui.js +93 -0
- package/dist/tools/dump-ui.js.map +1 -0
- package/dist/tools/find-on-screen.d.ts +27 -0
- package/dist/tools/find-on-screen.js +92 -0
- package/dist/tools/find-on-screen.js.map +1 -0
- package/dist/tools/install-app.d.ts +23 -0
- package/dist/tools/install-app.js +127 -0
- package/dist/tools/install-app.js.map +1 -0
- package/dist/tools/launch-and-wait.d.ts +27 -0
- package/dist/tools/launch-and-wait.js +103 -0
- package/dist/tools/launch-and-wait.js.map +1 -0
- package/dist/tools/launch-app.d.ts +20 -0
- package/dist/tools/launch-app.js +131 -0
- package/dist/tools/launch-app.js.map +1 -0
- package/dist/tools/launch-settings.d.ts +42 -0
- package/dist/tools/launch-settings.js +211 -0
- package/dist/tools/launch-settings.js.map +1 -0
- package/dist/tools/list-devices.d.ts +2 -0
- package/dist/tools/list-devices.js +35 -0
- package/dist/tools/list-devices.js.map +1 -0
- package/dist/tools/long-press-by-text.d.ts +3 -0
- package/dist/tools/long-press-by-text.js +99 -0
- package/dist/tools/long-press-by-text.js.map +1 -0
- package/dist/tools/open-setting.d.ts +55 -0
- package/dist/tools/open-setting.js +257 -0
- package/dist/tools/open-setting.js.map +1 -0
- package/dist/tools/read-logs.d.ts +2 -0
- package/dist/tools/read-logs.js +147 -0
- package/dist/tools/read-logs.js.map +1 -0
- package/dist/tools/screenshot-with-ui.d.ts +21 -0
- package/dist/tools/screenshot-with-ui.js +74 -0
- package/dist/tools/screenshot-with-ui.js.map +1 -0
- package/dist/tools/screenshot.d.ts +8 -0
- package/dist/tools/screenshot.js +97 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/scroll-until-visible.d.ts +23 -0
- package/dist/tools/scroll-until-visible.js +138 -0
- package/dist/tools/scroll-until-visible.js.map +1 -0
- package/dist/tools/start-pair-qr.d.ts +11 -0
- package/dist/tools/start-pair-qr.js +62 -0
- package/dist/tools/start-pair-qr.js.map +1 -0
- package/dist/tools/stop-app.d.ts +2 -0
- package/dist/tools/stop-app.js +63 -0
- package/dist/tools/stop-app.js.map +1 -0
- package/dist/tools/tap-by-content-desc.d.ts +17 -0
- package/dist/tools/tap-by-content-desc.js +97 -0
- package/dist/tools/tap-by-content-desc.js.map +1 -0
- package/dist/tools/tap-by-resource-id.d.ts +17 -0
- package/dist/tools/tap-by-resource-id.js +89 -0
- package/dist/tools/tap-by-resource-id.js.map +1 -0
- package/dist/tools/tap-by-text.d.ts +40 -0
- package/dist/tools/tap-by-text.js +180 -0
- package/dist/tools/tap-by-text.js.map +1 -0
- package/dist/tools/tap-text-verified.d.ts +37 -0
- package/dist/tools/tap-text-verified.js +111 -0
- package/dist/tools/tap-text-verified.js.map +1 -0
- package/dist/tools/uninstall-app.d.ts +2 -0
- package/dist/tools/uninstall-app.js +96 -0
- package/dist/tools/uninstall-app.js.map +1 -0
- package/dist/tools/wait-for-pair.d.ts +29 -0
- package/dist/tools/wait-for-pair.js +132 -0
- package/dist/tools/wait-for-pair.js.map +1 -0
- package/dist/tools/wait-for.d.ts +26 -0
- package/dist/tools/wait-for.js +109 -0
- package/dist/tools/wait-for.js.map +1 -0
- package/dist/uiDump.d.ts +34 -0
- package/dist/uiDump.js +108 -0
- package/dist/uiDump.js.map +1 -0
- package/dist/uiSelector.d.ts +21 -0
- package/dist/uiSelector.js +62 -0
- package/dist/uiSelector.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fetchUiDump, isInteresting } from "../uiDump.js";
|
|
3
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
4
|
+
import { deviceIdInput } from "../schemas.js";
|
|
5
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
6
|
+
const uiNodeOut = z.object({
|
|
7
|
+
text: z.string(),
|
|
8
|
+
content_desc: z.string(),
|
|
9
|
+
resource_id: z.string(),
|
|
10
|
+
class: z.string(),
|
|
11
|
+
package: z.string(),
|
|
12
|
+
bounds: z.object({
|
|
13
|
+
x1: z.number(),
|
|
14
|
+
y1: z.number(),
|
|
15
|
+
x2: z.number(),
|
|
16
|
+
y2: z.number(),
|
|
17
|
+
}),
|
|
18
|
+
center: z.object({ x: z.number(), y: z.number() }),
|
|
19
|
+
clickable: z.boolean(),
|
|
20
|
+
long_clickable: z.boolean(),
|
|
21
|
+
scrollable: z.boolean(),
|
|
22
|
+
focusable: z.boolean(),
|
|
23
|
+
focused: z.boolean(),
|
|
24
|
+
enabled: z.boolean(),
|
|
25
|
+
selected: z.boolean(),
|
|
26
|
+
checkable: z.boolean(),
|
|
27
|
+
checked: z.boolean(),
|
|
28
|
+
});
|
|
29
|
+
// Re-export for backwards-compatibility with the existing test file's import path.
|
|
30
|
+
export { parseUiHierarchy, isInteresting } from "../uiDump.js";
|
|
31
|
+
export function registerDumpUi(server) {
|
|
32
|
+
server.registerTool("dump_ui", {
|
|
33
|
+
title: "Dump current UI hierarchy",
|
|
34
|
+
description: "Capture the visible Android view hierarchy via `uiautomator dump`. " +
|
|
35
|
+
"Returns a flat list of elements with text, content-desc, resource-id, " +
|
|
36
|
+
"bounds, and a pre-computed center {x,y}. Feed matches into " +
|
|
37
|
+
"`tap_by_text`, `tap_by_resource_id`, or `tap_by_content_desc` " +
|
|
38
|
+
"instead of computing tap coordinates from a screenshot. " +
|
|
39
|
+
"Defaults to interactive_only=true, which keeps nodes that are " +
|
|
40
|
+
"clickable / long-clickable / scrollable / focusable OR have any of " +
|
|
41
|
+
"text / content-desc / resource-id. " +
|
|
42
|
+
"When the goal includes verifying the tap worked, prefer " +
|
|
43
|
+
"`tap_text_verified` (one call vs three) or `screenshot_with_ui` " +
|
|
44
|
+
"when you need both visual and semantic data.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
device_id: deviceIdInput,
|
|
47
|
+
interactive_only: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.optional()
|
|
50
|
+
.default(true)
|
|
51
|
+
.describe("Keep only nodes likely to matter for navigation. Set false to include layout containers and pure decorations."),
|
|
52
|
+
include_raw_xml: z
|
|
53
|
+
.boolean()
|
|
54
|
+
.optional()
|
|
55
|
+
.default(false)
|
|
56
|
+
.describe("Also return the raw uiautomator XML. Off by default because dense screens (heavy WebViews, long lists) can produce multi-MB payloads that risk breaching MCP transport size limits."),
|
|
57
|
+
},
|
|
58
|
+
outputSchema: {
|
|
59
|
+
device_id: z.string(),
|
|
60
|
+
node_count: z.number().int().nonnegative(),
|
|
61
|
+
nodes: z.array(uiNodeOut),
|
|
62
|
+
raw_xml: z.string().optional(),
|
|
63
|
+
},
|
|
64
|
+
annotations: {
|
|
65
|
+
readOnlyHint: true,
|
|
66
|
+
destructiveHint: false,
|
|
67
|
+
idempotentHint: false,
|
|
68
|
+
openWorldHint: false,
|
|
69
|
+
},
|
|
70
|
+
}, async ({ device_id, interactive_only, include_raw_xml }) => {
|
|
71
|
+
try {
|
|
72
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
73
|
+
const { nodes: all, raw_xml } = await fetchUiDump(deviceArgs);
|
|
74
|
+
const nodes = interactive_only ? all.filter(isInteresting) : all;
|
|
75
|
+
const summary = interactive_only
|
|
76
|
+
? `Parsed ${all.length} node${all.length === 1 ? "" : "s"} on ${serial}; ${nodes.length} interactive.`
|
|
77
|
+
: `Parsed ${nodes.length} node${nodes.length === 1 ? "" : "s"} on ${serial}.`;
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: summary }],
|
|
80
|
+
structuredContent: {
|
|
81
|
+
device_id: serial,
|
|
82
|
+
node_count: nodes.length,
|
|
83
|
+
nodes,
|
|
84
|
+
raw_xml: include_raw_xml ? raw_xml : undefined,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
return toolErrorFrom(err);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=dump-ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dump-ui.js","sourceRoot":"","sources":["../../src/tools/dump-ui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type UiNode } from "../uiDump.js";
|
|
3
|
+
import { type Selector } from "../uiSelector.js";
|
|
4
|
+
export interface FindOnScreenMatch {
|
|
5
|
+
text: string;
|
|
6
|
+
content_desc: string;
|
|
7
|
+
resource_id: string;
|
|
8
|
+
bounds: {
|
|
9
|
+
x1: number;
|
|
10
|
+
y1: number;
|
|
11
|
+
x2: number;
|
|
12
|
+
y2: number;
|
|
13
|
+
};
|
|
14
|
+
center: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
};
|
|
18
|
+
clickable: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface FindOnScreenResult {
|
|
21
|
+
found: boolean;
|
|
22
|
+
count: number;
|
|
23
|
+
matches: FindOnScreenMatch[];
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function buildFindOnScreenResult(nodes: UiNode[], selector: Selector): FindOnScreenResult;
|
|
27
|
+
export declare function registerFindOnScreen(server: McpServer): void;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fetchUiDump } from "../uiDump.js";
|
|
3
|
+
import { findNodes } from "../uiSelector.js";
|
|
4
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
5
|
+
import { deviceIdInput } from "../schemas.js";
|
|
6
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
7
|
+
const MAX_MATCHES = 20;
|
|
8
|
+
export function buildFindOnScreenResult(nodes, selector) {
|
|
9
|
+
const matched = findNodes(nodes, selector);
|
|
10
|
+
const matches = matched.slice(0, MAX_MATCHES).map((n) => ({
|
|
11
|
+
text: n.text,
|
|
12
|
+
content_desc: n.content_desc,
|
|
13
|
+
resource_id: n.resource_id,
|
|
14
|
+
bounds: n.bounds,
|
|
15
|
+
center: n.center,
|
|
16
|
+
clickable: n.clickable,
|
|
17
|
+
}));
|
|
18
|
+
return {
|
|
19
|
+
found: matched.length > 0,
|
|
20
|
+
count: matched.length,
|
|
21
|
+
matches,
|
|
22
|
+
truncated: matched.length > MAX_MATCHES,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function registerFindOnScreen(server) {
|
|
26
|
+
server.registerTool("find_on_screen", {
|
|
27
|
+
title: "Find UI elements on the current screen (no action)",
|
|
28
|
+
description: "Pure query — no action taken. Returns up to 20 matches for the given selector. " +
|
|
29
|
+
"Use to assert state, disambiguate between multiple matches, or check whether " +
|
|
30
|
+
"something is visible before deciding what to do.",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
by: z
|
|
33
|
+
.enum(["text", "resource_id", "content_desc"])
|
|
34
|
+
.describe("Which attribute to match on."),
|
|
35
|
+
value: z.string().min(1).describe("Value to look up."),
|
|
36
|
+
exact: z
|
|
37
|
+
.boolean()
|
|
38
|
+
.optional()
|
|
39
|
+
.default(false)
|
|
40
|
+
.describe("When true, requires equality (ignored for resource_id which is always exact)."),
|
|
41
|
+
device_id: deviceIdInput,
|
|
42
|
+
},
|
|
43
|
+
outputSchema: {
|
|
44
|
+
device_id: z.string(),
|
|
45
|
+
found: z.boolean(),
|
|
46
|
+
count: z.number().int().nonnegative(),
|
|
47
|
+
matches: z.array(z.object({
|
|
48
|
+
text: z.string(),
|
|
49
|
+
content_desc: z.string(),
|
|
50
|
+
resource_id: z.string(),
|
|
51
|
+
bounds: z.object({
|
|
52
|
+
x1: z.number(),
|
|
53
|
+
y1: z.number(),
|
|
54
|
+
x2: z.number(),
|
|
55
|
+
y2: z.number(),
|
|
56
|
+
}),
|
|
57
|
+
center: z.object({ x: z.number(), y: z.number() }),
|
|
58
|
+
clickable: z.boolean(),
|
|
59
|
+
})),
|
|
60
|
+
truncated: z.boolean(),
|
|
61
|
+
},
|
|
62
|
+
annotations: {
|
|
63
|
+
readOnlyHint: true,
|
|
64
|
+
destructiveHint: false,
|
|
65
|
+
idempotentHint: true,
|
|
66
|
+
openWorldHint: false,
|
|
67
|
+
},
|
|
68
|
+
}, async ({ by, value, exact, device_id }) => {
|
|
69
|
+
try {
|
|
70
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
71
|
+
const { nodes } = await fetchUiDump(deviceArgs);
|
|
72
|
+
const selector = by === "resource_id"
|
|
73
|
+
? { by, value }
|
|
74
|
+
: { by, value, exact };
|
|
75
|
+
const result = buildFindOnScreenResult(nodes, selector);
|
|
76
|
+
const summary = result.found
|
|
77
|
+
? `Found ${result.count} match${result.count === 1 ? "" : "es"} for ${by}="${value}" on ${serial}${result.truncated ? ` (showing first ${MAX_MATCHES})` : ""}.`
|
|
78
|
+
: `No matches for ${by}="${value}" on ${serial}.`;
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: summary }],
|
|
81
|
+
structuredContent: {
|
|
82
|
+
device_id: serial,
|
|
83
|
+
...result,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return toolErrorFrom(err);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=find-on-screen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-on-screen.js","sourceRoot":"","sources":["../../src/tools/find-on-screen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function extractInstallFailure(stdout: string, stderr: string): {
|
|
3
|
+
code?: string;
|
|
4
|
+
raw: string;
|
|
5
|
+
};
|
|
6
|
+
export interface PerformInstallParams {
|
|
7
|
+
apkPath: string;
|
|
8
|
+
deviceArgs: string[];
|
|
9
|
+
reinstall: boolean;
|
|
10
|
+
grantRuntimePermissions: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface InstallResult {
|
|
13
|
+
success: boolean;
|
|
14
|
+
failureCode?: string;
|
|
15
|
+
rawFailure?: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run `adb install` and parse the result. Caller is responsible for verifying
|
|
20
|
+
* the apk exists and resolving device args.
|
|
21
|
+
*/
|
|
22
|
+
export declare function performInstall(params: PerformInstallParams): Promise<InstallResult>;
|
|
23
|
+
export declare function registerInstallApp(server: McpServer): void;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAdb } from "../adb.js";
|
|
3
|
+
import { loadFlingConfig, resolveBuildCwd } from "../config.js";
|
|
4
|
+
import { resolveApk } from "../apkResolver.js";
|
|
5
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
6
|
+
import { deviceIdInput } from "../schemas.js";
|
|
7
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
8
|
+
const INSTALL_TIMEOUT_MS = 180_000;
|
|
9
|
+
const INSTALL_FAILURE_HINTS = {
|
|
10
|
+
INSTALL_FAILED_INSUFFICIENT_STORAGE: "The device is out of free space. Uninstall unused apps or clear caches and retry.",
|
|
11
|
+
INSTALL_FAILED_UPDATE_INCOMPATIBLE: "An app with the same package name is already installed with a different signing certificate. Uninstall it first (`uninstall_app`), then retry.",
|
|
12
|
+
INSTALL_FAILED_VERSION_DOWNGRADE: "The installed version is newer than the APK you're trying to install. Either bump the APK's versionCode or uninstall the existing app first.",
|
|
13
|
+
INSTALL_PARSE_FAILED_NO_CERTIFICATES: "The APK is unsigned. Sign it with apksigner (or build a signed variant) before installing.",
|
|
14
|
+
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: "The APK's signing certificates are inconsistent. Rebuild and re-sign.",
|
|
15
|
+
INSTALL_FAILED_MISSING_SHARED_LIBRARY: "The app depends on a shared library that isn't present on this device.",
|
|
16
|
+
INSTALL_FAILED_OLDER_SDK: "The APK requires a newer Android version than the device runs.",
|
|
17
|
+
INSTALL_FAILED_NEWER_SDK: "The APK targets an older Android version than the device supports.",
|
|
18
|
+
INSTALL_FAILED_NO_MATCHING_ABIS: "The APK doesn't include native libraries for the device's CPU architecture.",
|
|
19
|
+
INSTALL_FAILED_USER_RESTRICTED: "Installation was blocked by the user or a device policy.",
|
|
20
|
+
};
|
|
21
|
+
export function extractInstallFailure(stdout, stderr) {
|
|
22
|
+
const haystack = `${stdout}\n${stderr}`.trim();
|
|
23
|
+
const codeMatch = haystack.match(/INSTALL_(?:FAILED|PARSE_FAILED)_[A-Z_]+/);
|
|
24
|
+
const lines = haystack.split(/\r?\n/);
|
|
25
|
+
const markerIdx = lines.findIndex((l) => /adb: failed|^Failure\b|INSTALL_(?:FAILED|PARSE_FAILED)_/.test(l));
|
|
26
|
+
const raw = markerIdx >= 0
|
|
27
|
+
? lines.slice(markerIdx, markerIdx + 3).join("\n").trim()
|
|
28
|
+
: haystack;
|
|
29
|
+
return { code: codeMatch?.[0], raw };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run `adb install` and parse the result. Caller is responsible for verifying
|
|
33
|
+
* the apk exists and resolving device args.
|
|
34
|
+
*/
|
|
35
|
+
export async function performInstall(params) {
|
|
36
|
+
const installArgs = ["install"];
|
|
37
|
+
if (params.reinstall)
|
|
38
|
+
installArgs.push("-r");
|
|
39
|
+
if (params.grantRuntimePermissions)
|
|
40
|
+
installArgs.push("-g");
|
|
41
|
+
installArgs.push(params.apkPath);
|
|
42
|
+
const { stdout, stderr } = await runAdb([...params.deviceArgs, ...installArgs], {
|
|
43
|
+
timeoutMs: INSTALL_TIMEOUT_MS,
|
|
44
|
+
});
|
|
45
|
+
const combined = `${stdout}\n${stderr}`;
|
|
46
|
+
if (/^Success\b/m.test(combined)) {
|
|
47
|
+
return { success: true, message: `Installed ${params.apkPath}` };
|
|
48
|
+
}
|
|
49
|
+
const { code, raw } = extractInstallFailure(stdout, stderr);
|
|
50
|
+
const hint = code ? INSTALL_FAILURE_HINTS[code] : undefined;
|
|
51
|
+
const message = `Install failed: ${raw}${hint ? `\n\n→ ${hint}` : ""}`;
|
|
52
|
+
return { success: false, failureCode: code, rawFailure: raw, message };
|
|
53
|
+
}
|
|
54
|
+
export function registerInstallApp(server) {
|
|
55
|
+
server.registerTool("install_app", {
|
|
56
|
+
title: "Install an APK on a device",
|
|
57
|
+
description: "Push an .apk file to a connected Android device and install it via `adb install`. " +
|
|
58
|
+
"By default the install is a reinstall (-r) which preserves existing app data. " +
|
|
59
|
+
"When `apk_path` is omitted, Fling resolves it from fling.config.json (apkPath / apkGlob).",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
apk_path: z
|
|
62
|
+
.string()
|
|
63
|
+
.min(1)
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Path to the .apk file on the host machine. If omitted, falls back to fling.config.json."),
|
|
66
|
+
device_id: deviceIdInput,
|
|
67
|
+
reinstall: z
|
|
68
|
+
.boolean()
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("Pass -r (reinstall keeping data). Default true."),
|
|
71
|
+
grant_runtime_permissions: z
|
|
72
|
+
.boolean()
|
|
73
|
+
.optional()
|
|
74
|
+
.describe("Pass -g (grant all runtime permissions on install). Default false."),
|
|
75
|
+
cwd: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Starting directory for config lookup. Defaults to the MCP server's cwd."),
|
|
79
|
+
},
|
|
80
|
+
outputSchema: {
|
|
81
|
+
device_id: z.string(),
|
|
82
|
+
apk_path: z.string(),
|
|
83
|
+
apk_source: z.string(),
|
|
84
|
+
success: z.boolean(),
|
|
85
|
+
failure_code: z.string().optional(),
|
|
86
|
+
message: z.string(),
|
|
87
|
+
},
|
|
88
|
+
annotations: {
|
|
89
|
+
readOnlyHint: false,
|
|
90
|
+
destructiveHint: true,
|
|
91
|
+
idempotentHint: true,
|
|
92
|
+
openWorldHint: false,
|
|
93
|
+
},
|
|
94
|
+
}, async ({ apk_path, device_id, reinstall, grant_runtime_permissions, cwd }) => {
|
|
95
|
+
try {
|
|
96
|
+
const loaded = await loadFlingConfig(cwd ?? process.cwd());
|
|
97
|
+
const buildCwd = resolveBuildCwd(loaded);
|
|
98
|
+
const apk = await resolveApk(apk_path, loaded.config, buildCwd);
|
|
99
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
100
|
+
const result = await performInstall({
|
|
101
|
+
apkPath: apk.path,
|
|
102
|
+
deviceArgs,
|
|
103
|
+
reinstall: reinstall !== false,
|
|
104
|
+
grantRuntimePermissions: !!grant_runtime_permissions,
|
|
105
|
+
});
|
|
106
|
+
const text = result.success
|
|
107
|
+
? `Installed ${apk.path} on ${serial}. (apk source: ${apk.source})`
|
|
108
|
+
: `Install failed on ${serial}.\n\n${result.message}`;
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text }],
|
|
111
|
+
structuredContent: {
|
|
112
|
+
device_id: serial,
|
|
113
|
+
apk_path: apk.path,
|
|
114
|
+
apk_source: apk.source,
|
|
115
|
+
success: result.success,
|
|
116
|
+
failure_code: result.failureCode,
|
|
117
|
+
message: text,
|
|
118
|
+
},
|
|
119
|
+
isError: !result.success,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
return toolErrorFrom(err);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=install-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-app.js","sourceRoot":"","sources":["../../src/tools/install-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { UiNode } from "../uiDump.js";
|
|
3
|
+
export type ReadySelector = {
|
|
4
|
+
by: "text" | "resource_id";
|
|
5
|
+
value: string;
|
|
6
|
+
};
|
|
7
|
+
export interface LaunchAndWaitInput {
|
|
8
|
+
packageName: string;
|
|
9
|
+
readyWhen: ReadySelector;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
pollIntervalMs?: number;
|
|
12
|
+
launchFn: (pkg: string) => Promise<void>;
|
|
13
|
+
dumpFn: () => Promise<UiNode[]>;
|
|
14
|
+
sleepFn: (ms: number) => Promise<void>;
|
|
15
|
+
nowFn: () => number;
|
|
16
|
+
}
|
|
17
|
+
export interface LaunchAndWaitResult {
|
|
18
|
+
ready: boolean;
|
|
19
|
+
attempts: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Launch a package via monkey, then poll the UI hierarchy until the
|
|
23
|
+
* readyWhen selector appears (or timeout). Saves the launch → wait →
|
|
24
|
+
* screenshot → check round-trip loop.
|
|
25
|
+
*/
|
|
26
|
+
export declare function launchAndWait(input: LaunchAndWaitInput): Promise<LaunchAndWaitResult>;
|
|
27
|
+
export declare function registerLaunchAndWait(server: McpServer): void;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runAdb } from "../adb.js";
|
|
3
|
+
import { fetchUiDump } from "../uiDump.js";
|
|
4
|
+
import { resolveDeviceArgs } from "../devices.js";
|
|
5
|
+
import { FlingError } from "../errors.js";
|
|
6
|
+
import { deviceIdInput } from "../schemas.js";
|
|
7
|
+
import { toolErrorFrom } from "../toolResult.js";
|
|
8
|
+
function matchesSelector(nodes, sel) {
|
|
9
|
+
if (sel.by === "text")
|
|
10
|
+
return nodes.some((n) => n.text.includes(sel.value));
|
|
11
|
+
return nodes.some((n) => n.resource_id === sel.value);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Launch a package via monkey, then poll the UI hierarchy until the
|
|
15
|
+
* readyWhen selector appears (or timeout). Saves the launch → wait →
|
|
16
|
+
* screenshot → check round-trip loop.
|
|
17
|
+
*/
|
|
18
|
+
export async function launchAndWait(input) {
|
|
19
|
+
const timeoutMs = input.timeoutMs ?? 10_000;
|
|
20
|
+
const pollIntervalMs = input.pollIntervalMs ?? 250;
|
|
21
|
+
await input.launchFn(input.packageName);
|
|
22
|
+
const start = input.nowFn();
|
|
23
|
+
let attempts = 0;
|
|
24
|
+
while (input.nowFn() - start < timeoutMs) {
|
|
25
|
+
attempts++;
|
|
26
|
+
const dump = await input.dumpFn();
|
|
27
|
+
if (matchesSelector(dump, input.readyWhen)) {
|
|
28
|
+
return { ready: true, attempts };
|
|
29
|
+
}
|
|
30
|
+
await input.sleepFn(pollIntervalMs);
|
|
31
|
+
}
|
|
32
|
+
throw new FlingError("UI_WAIT_TIMEOUT", `Ready selector ${input.readyWhen.by}="${input.readyWhen.value}" did not appear within ${timeoutMs}ms after launching ${input.packageName}.`);
|
|
33
|
+
}
|
|
34
|
+
export function registerLaunchAndWait(server) {
|
|
35
|
+
server.registerTool("launch_and_wait", {
|
|
36
|
+
title: "Launch an app and wait until it's ready",
|
|
37
|
+
description: "Launch the package via monkey then poll dump_ui until a ready " +
|
|
38
|
+
"selector (text or resource_id) appears. Prefer over " +
|
|
39
|
+
"launch_app + repeated dump_ui calls when you need to wait for the " +
|
|
40
|
+
"UI to become interactive.",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
device_id: deviceIdInput,
|
|
43
|
+
package_name: z.string().min(1),
|
|
44
|
+
ready_when_text: z.string().optional(),
|
|
45
|
+
ready_when_resource_id: z.string().optional(),
|
|
46
|
+
timeout_ms: z.number().int().positive().optional(),
|
|
47
|
+
},
|
|
48
|
+
outputSchema: {
|
|
49
|
+
device_id: z.string(),
|
|
50
|
+
ready: z.boolean(),
|
|
51
|
+
attempts: z.number().int().nonnegative(),
|
|
52
|
+
},
|
|
53
|
+
annotations: {
|
|
54
|
+
readOnlyHint: false,
|
|
55
|
+
destructiveHint: false,
|
|
56
|
+
idempotentHint: false,
|
|
57
|
+
openWorldHint: false,
|
|
58
|
+
},
|
|
59
|
+
}, async ({ device_id, package_name, ready_when_text, ready_when_resource_id, timeout_ms, }) => {
|
|
60
|
+
try {
|
|
61
|
+
if (!ready_when_text && !ready_when_resource_id) {
|
|
62
|
+
throw new FlingError("INVALID_INPUT", "Provide either ready_when_text or ready_when_resource_id.");
|
|
63
|
+
}
|
|
64
|
+
const readyWhen = ready_when_text
|
|
65
|
+
? { by: "text", value: ready_when_text }
|
|
66
|
+
: { by: "resource_id", value: ready_when_resource_id };
|
|
67
|
+
const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
|
|
68
|
+
const result = await launchAndWait({
|
|
69
|
+
packageName: package_name,
|
|
70
|
+
readyWhen,
|
|
71
|
+
timeoutMs: timeout_ms,
|
|
72
|
+
launchFn: async (pkg) => {
|
|
73
|
+
await runAdb([
|
|
74
|
+
...deviceArgs,
|
|
75
|
+
"shell",
|
|
76
|
+
"monkey",
|
|
77
|
+
"-p",
|
|
78
|
+
pkg,
|
|
79
|
+
"-c",
|
|
80
|
+
"android.intent.category.LAUNCHER",
|
|
81
|
+
"1",
|
|
82
|
+
]);
|
|
83
|
+
},
|
|
84
|
+
dumpFn: async () => (await fetchUiDump(deviceArgs)).nodes,
|
|
85
|
+
sleepFn: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
86
|
+
nowFn: () => Date.now(),
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: `Launched ${package_name} on ${serial}; ready after ${result.attempts} dump(s).`,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
structuredContent: { device_id: serial, ...result },
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
return toolErrorFrom(err);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=launch-and-wait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launch-and-wait.js","sourceRoot":"","sources":["../../src/tools/launch-and-wait.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function validatePackage(name: string): void;
|
|
3
|
+
export declare function validateActivity(activity: string): void;
|
|
4
|
+
export interface PerformLaunchParams {
|
|
5
|
+
packageName: string;
|
|
6
|
+
activity?: string;
|
|
7
|
+
deviceArgs: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface LaunchResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
raw: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Start an app on the device. With activity: `am start -W -n pkg/activity`.
|
|
16
|
+
* Without: `monkey -p pkg -c LAUNCHER 1`. Note: success means the launch
|
|
17
|
+
* intent was dispatched, not that the app is stable.
|
|
18
|
+
*/
|
|
19
|
+
export declare function performLaunch(params: PerformLaunchParams): Promise<LaunchResult>;
|
|
20
|
+
export declare function registerLaunchApp(server: McpServer): void;
|