@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.
Files changed (133) hide show
  1. package/dist/adb.d.ts +18 -0
  2. package/dist/adb.js +109 -0
  3. package/dist/adb.js.map +1 -0
  4. package/dist/apkFinder.d.ts +19 -0
  5. package/dist/apkFinder.js +113 -0
  6. package/dist/apkFinder.js.map +1 -0
  7. package/dist/apkResolver.d.ts +14 -0
  8. package/dist/apkResolver.js +55 -0
  9. package/dist/apkResolver.js.map +1 -0
  10. package/dist/config.d.ts +44 -0
  11. package/dist/config.js +113 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/devices.d.ts +24 -0
  14. package/dist/devices.js +143 -0
  15. package/dist/devices.js.map +1 -0
  16. package/dist/errors.d.ts +13 -0
  17. package/dist/errors.js +22 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/featureFlags.d.ts +6 -0
  20. package/dist/featureFlags.js +11 -0
  21. package/dist/featureFlags.js.map +1 -0
  22. package/dist/gradle.d.ts +39 -0
  23. package/dist/gradle.js +129 -0
  24. package/dist/gradle.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.js +76 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/mdns.d.ts +19 -0
  29. package/dist/mdns.js +68 -0
  30. package/dist/mdns.js.map +1 -0
  31. package/dist/pairing.d.ts +52 -0
  32. package/dist/pairing.js +212 -0
  33. package/dist/pairing.js.map +1 -0
  34. package/dist/qrText.d.ts +5 -0
  35. package/dist/qrText.js +8 -0
  36. package/dist/qrText.js.map +1 -0
  37. package/dist/schemas.d.ts +31 -0
  38. package/dist/schemas.js +26 -0
  39. package/dist/schemas.js.map +1 -0
  40. package/dist/shellFraming.d.ts +28 -0
  41. package/dist/shellFraming.js +43 -0
  42. package/dist/shellFraming.js.map +1 -0
  43. package/dist/shellPool.d.ts +54 -0
  44. package/dist/shellPool.js +181 -0
  45. package/dist/shellPool.js.map +1 -0
  46. package/dist/toolResult.d.ts +10 -0
  47. package/dist/toolResult.js +11 -0
  48. package/dist/toolResult.js.map +1 -0
  49. package/dist/tools/build-app.d.ts +2 -0
  50. package/dist/tools/build-app.js +74 -0
  51. package/dist/tools/build-app.js.map +1 -0
  52. package/dist/tools/deploy-and-run.d.ts +2 -0
  53. package/dist/tools/deploy-and-run.js +195 -0
  54. package/dist/tools/deploy-and-run.js.map +1 -0
  55. package/dist/tools/device-state.d.ts +11 -0
  56. package/dist/tools/device-state.js +140 -0
  57. package/dist/tools/device-state.js.map +1 -0
  58. package/dist/tools/dismiss-dialog.d.ts +15 -0
  59. package/dist/tools/dismiss-dialog.js +89 -0
  60. package/dist/tools/dismiss-dialog.js.map +1 -0
  61. package/dist/tools/dump-ui.d.ts +4 -0
  62. package/dist/tools/dump-ui.js +93 -0
  63. package/dist/tools/dump-ui.js.map +1 -0
  64. package/dist/tools/find-on-screen.d.ts +27 -0
  65. package/dist/tools/find-on-screen.js +92 -0
  66. package/dist/tools/find-on-screen.js.map +1 -0
  67. package/dist/tools/install-app.d.ts +23 -0
  68. package/dist/tools/install-app.js +127 -0
  69. package/dist/tools/install-app.js.map +1 -0
  70. package/dist/tools/launch-and-wait.d.ts +27 -0
  71. package/dist/tools/launch-and-wait.js +103 -0
  72. package/dist/tools/launch-and-wait.js.map +1 -0
  73. package/dist/tools/launch-app.d.ts +20 -0
  74. package/dist/tools/launch-app.js +131 -0
  75. package/dist/tools/launch-app.js.map +1 -0
  76. package/dist/tools/launch-settings.d.ts +42 -0
  77. package/dist/tools/launch-settings.js +211 -0
  78. package/dist/tools/launch-settings.js.map +1 -0
  79. package/dist/tools/list-devices.d.ts +2 -0
  80. package/dist/tools/list-devices.js +35 -0
  81. package/dist/tools/list-devices.js.map +1 -0
  82. package/dist/tools/long-press-by-text.d.ts +3 -0
  83. package/dist/tools/long-press-by-text.js +99 -0
  84. package/dist/tools/long-press-by-text.js.map +1 -0
  85. package/dist/tools/open-setting.d.ts +55 -0
  86. package/dist/tools/open-setting.js +257 -0
  87. package/dist/tools/open-setting.js.map +1 -0
  88. package/dist/tools/read-logs.d.ts +2 -0
  89. package/dist/tools/read-logs.js +147 -0
  90. package/dist/tools/read-logs.js.map +1 -0
  91. package/dist/tools/screenshot-with-ui.d.ts +21 -0
  92. package/dist/tools/screenshot-with-ui.js +74 -0
  93. package/dist/tools/screenshot-with-ui.js.map +1 -0
  94. package/dist/tools/screenshot.d.ts +8 -0
  95. package/dist/tools/screenshot.js +97 -0
  96. package/dist/tools/screenshot.js.map +1 -0
  97. package/dist/tools/scroll-until-visible.d.ts +23 -0
  98. package/dist/tools/scroll-until-visible.js +138 -0
  99. package/dist/tools/scroll-until-visible.js.map +1 -0
  100. package/dist/tools/start-pair-qr.d.ts +11 -0
  101. package/dist/tools/start-pair-qr.js +62 -0
  102. package/dist/tools/start-pair-qr.js.map +1 -0
  103. package/dist/tools/stop-app.d.ts +2 -0
  104. package/dist/tools/stop-app.js +63 -0
  105. package/dist/tools/stop-app.js.map +1 -0
  106. package/dist/tools/tap-by-content-desc.d.ts +17 -0
  107. package/dist/tools/tap-by-content-desc.js +97 -0
  108. package/dist/tools/tap-by-content-desc.js.map +1 -0
  109. package/dist/tools/tap-by-resource-id.d.ts +17 -0
  110. package/dist/tools/tap-by-resource-id.js +89 -0
  111. package/dist/tools/tap-by-resource-id.js.map +1 -0
  112. package/dist/tools/tap-by-text.d.ts +40 -0
  113. package/dist/tools/tap-by-text.js +180 -0
  114. package/dist/tools/tap-by-text.js.map +1 -0
  115. package/dist/tools/tap-text-verified.d.ts +37 -0
  116. package/dist/tools/tap-text-verified.js +111 -0
  117. package/dist/tools/tap-text-verified.js.map +1 -0
  118. package/dist/tools/uninstall-app.d.ts +2 -0
  119. package/dist/tools/uninstall-app.js +96 -0
  120. package/dist/tools/uninstall-app.js.map +1 -0
  121. package/dist/tools/wait-for-pair.d.ts +29 -0
  122. package/dist/tools/wait-for-pair.js +132 -0
  123. package/dist/tools/wait-for-pair.js.map +1 -0
  124. package/dist/tools/wait-for.d.ts +26 -0
  125. package/dist/tools/wait-for.js +109 -0
  126. package/dist/tools/wait-for.js.map +1 -0
  127. package/dist/uiDump.d.ts +34 -0
  128. package/dist/uiDump.js +108 -0
  129. package/dist/uiDump.js.map +1 -0
  130. package/dist/uiSelector.d.ts +21 -0
  131. package/dist/uiSelector.js +62 -0
  132. package/dist/uiSelector.js.map +1 -0
  133. package/package.json +67 -0
@@ -0,0 +1,131 @@
1
+ import { z } from "zod";
2
+ import { runAdb } from "../adb.js";
3
+ import { loadFlingConfig } from "../config.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
+ const PACKAGE_RE = /^[A-Za-z][\w]*(\.[A-Za-z][\w]*)+$/;
9
+ const ACTIVITY_RE = /^\.?[A-Za-z][\w$]*(\.[A-Za-z][\w$]*)*$/;
10
+ export function validatePackage(name) {
11
+ if (!PACKAGE_RE.test(name)) {
12
+ throw new FlingError("INVALID_INPUT", `Invalid Android package name: "${name}". Expected dotted identifier like com.example.app.`);
13
+ }
14
+ }
15
+ export function validateActivity(activity) {
16
+ if (!ACTIVITY_RE.test(activity)) {
17
+ throw new FlingError("INVALID_INPUT", `Invalid activity name: "${activity}". Expected a class name like MainActivity, .MainActivity, or com.example.MainActivity.`);
18
+ }
19
+ }
20
+ /**
21
+ * Start an app on the device. With activity: `am start -W -n pkg/activity`.
22
+ * Without: `monkey -p pkg -c LAUNCHER 1`. Note: success means the launch
23
+ * intent was dispatched, not that the app is stable.
24
+ */
25
+ export async function performLaunch(params) {
26
+ const shellCmd = params.activity
27
+ ? ["shell", "am", "start", "-W", "-n", `${params.packageName}/${params.activity}`]
28
+ : [
29
+ "shell",
30
+ "monkey",
31
+ "-p",
32
+ params.packageName,
33
+ "-c",
34
+ "android.intent.category.LAUNCHER",
35
+ "1",
36
+ ];
37
+ const { stdout, stderr } = await runAdb([...params.deviceArgs, ...shellCmd]);
38
+ const combined = `${stdout}\n${stderr}`;
39
+ const failureRe = /No activities found to run|Monkey aborted|Error: (?:Activity class|Activity not started)|java\.lang\.SecurityException/;
40
+ const successRe = params.activity ? /Status:\s*ok/i : /Events injected:\s*1/;
41
+ if (failureRe.test(combined) || !successRe.test(combined)) {
42
+ const reason = combined.trim().split(/\r?\n/).slice(0, 8).join("\n");
43
+ return {
44
+ success: false,
45
+ message: `Launch dispatch failed for ${params.packageName}.`,
46
+ raw: reason,
47
+ };
48
+ }
49
+ return {
50
+ success: true,
51
+ message: params.activity
52
+ ? `Launched ${params.packageName}/${params.activity}`
53
+ : `Launched ${params.packageName} (default activity)`,
54
+ raw: combined.trim(),
55
+ };
56
+ }
57
+ export function registerLaunchApp(server) {
58
+ server.registerTool("launch_app", {
59
+ title: "Launch an Android app",
60
+ description: "Start an installed app on the device. If `activity` is omitted, the default " +
61
+ "launcher activity is started (via `monkey`). If `activity` is given, it is " +
62
+ "passed to `am start -n <package>/<activity>` directly. " +
63
+ "When `package_name` is omitted, falls back to fling.config.json (packageName, mainActivity). " +
64
+ "Note: success means the launch intent was dispatched, not that the app is " +
65
+ "stable — an app that crashes immediately after onCreate still returns success. " +
66
+ "When you need to wait for a specific UI element before proceeding, prefer " +
67
+ "`launch_and_wait` which polls dump_ui internally.",
68
+ inputSchema: {
69
+ package_name: z
70
+ .string()
71
+ .min(1)
72
+ .optional()
73
+ .describe("App package name (e.g. com.example.app). Optional if set in fling.config.json."),
74
+ activity: z
75
+ .string()
76
+ .min(1)
77
+ .optional()
78
+ .describe("Optional activity. Use a leading dot for relative (e.g. .MainActivity) or a fully-qualified class name."),
79
+ device_id: deviceIdInput,
80
+ cwd: z
81
+ .string()
82
+ .optional()
83
+ .describe("Starting directory for config lookup. Defaults to the MCP server's cwd."),
84
+ },
85
+ outputSchema: {
86
+ device_id: z.string(),
87
+ package_name: z.string(),
88
+ activity: z.string().optional(),
89
+ success: z.boolean(),
90
+ message: z.string(),
91
+ },
92
+ annotations: {
93
+ readOnlyHint: false,
94
+ destructiveHint: false,
95
+ idempotentHint: false,
96
+ openWorldHint: false,
97
+ },
98
+ }, async ({ package_name, activity, device_id, cwd }) => {
99
+ try {
100
+ const loaded = await loadFlingConfig(cwd ?? process.cwd());
101
+ const pkg = package_name ?? loaded.config.packageName;
102
+ if (!pkg) {
103
+ throw new FlingError("CONFIG_MISSING", "package_name was not provided and no packageName is set in fling.config.json.");
104
+ }
105
+ const act = activity ?? loaded.config.mainActivity;
106
+ validatePackage(pkg);
107
+ if (act)
108
+ validateActivity(act);
109
+ const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
110
+ const result = await performLaunch({ packageName: pkg, activity: act, deviceArgs });
111
+ const text = result.success
112
+ ? `${result.message} on ${serial}.`
113
+ : `${result.message} (on ${serial})\n\n${result.raw}`;
114
+ return {
115
+ content: [{ type: "text", text }],
116
+ structuredContent: {
117
+ device_id: serial,
118
+ package_name: pkg,
119
+ activity: act,
120
+ success: result.success,
121
+ message: text,
122
+ },
123
+ isError: !result.success,
124
+ };
125
+ }
126
+ catch (err) {
127
+ return toolErrorFrom(err);
128
+ }
129
+ });
130
+ }
131
+ //# sourceMappingURL=launch-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-app.js","sourceRoot":"","sources":["../../src/tools/launch-app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const SETTINGS_ACTION_ALLOWLIST: ReadonlySet<string>;
3
+ /**
4
+ * Accepts either the bare suffix (e.g. "WIFI_SETTINGS") or the fully qualified
5
+ * action (e.g. "android.settings.WIFI_SETTINGS") and returns the fully
6
+ * qualified form. Throws FlingError("INVALID_INPUT") on anything else — most
7
+ * importantly, on actions outside the allowlist, which is the guard against
8
+ * using this tool to fire arbitrary intents.
9
+ */
10
+ export declare function normalizeSettingsAction(input: string): string;
11
+ /**
12
+ * Strictly validates the `-d` payload. The only documented use for a data URI
13
+ * on a built-in settings intent is APPLICATION_DETAILS_SETTINGS, which expects
14
+ * `package:<dotted-android-id>`. Anything else is rejected to prevent arbitrary
15
+ * URI dispatch via this tool.
16
+ */
17
+ export declare function validateSettingsDataUri(uri: string): void;
18
+ export interface BuildSettingsAmArgsParams {
19
+ action: string;
20
+ dataUri?: string;
21
+ deviceArgs: string[];
22
+ }
23
+ export declare function buildSettingsAmArgs(params: BuildSettingsAmArgsParams): string[];
24
+ export interface SettingsLaunchResult {
25
+ success: boolean;
26
+ message: string;
27
+ raw: string;
28
+ }
29
+ /**
30
+ * Interpret `am start` output. Mirrors performLaunch in launch-app.ts: the
31
+ * activity manager merges device-side stderr into adb stdout in some Android
32
+ * builds, so we concatenate both and look for known failure markers OR the
33
+ * absence of `Status: ok` (which `am start -W` always emits on success).
34
+ */
35
+ export declare function interpretSettingsResult(stdout: string, stderr: string): SettingsLaunchResult;
36
+ export interface PerformLaunchSettingsParams {
37
+ action: string;
38
+ dataUri?: string;
39
+ deviceArgs: string[];
40
+ }
41
+ export declare function performLaunchSettings(params: PerformLaunchSettingsParams): Promise<SettingsLaunchResult>;
42
+ export declare function registerLaunchSettings(server: McpServer): void;
@@ -0,0 +1,211 @@
1
+ import { z } from "zod";
2
+ import { runAdb } from "../adb.js";
3
+ import { resolveDeviceArgs } from "../devices.js";
4
+ import { FlingError } from "../errors.js";
5
+ import { deviceIdInput } from "../schemas.js";
6
+ import { toolErrorFrom } from "../toolResult.js";
7
+ const ACTION_PREFIX = "android.settings.";
8
+ const SUFFIX_RE = /^[A-Z][A-Z0-9_]*$/;
9
+ const DATA_URI_RE = /^package:[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
10
+ // Standard android.provider.Settings.ACTION_* values that point at a built-in
11
+ // Settings screen reachable via `am start -a android.settings.<X>`. Restricted
12
+ // to a known set to avoid arbitrary intent dispatch through this tool.
13
+ export const SETTINGS_ACTION_ALLOWLIST = new Set([
14
+ "ACCESSIBILITY_SETTINGS",
15
+ "AIRPLANE_MODE_SETTINGS",
16
+ "APN_SETTINGS",
17
+ "APPLICATION_DETAILS_SETTINGS",
18
+ "APPLICATION_DEVELOPMENT_SETTINGS",
19
+ "APPLICATION_SETTINGS",
20
+ "APP_NOTIFICATION_SETTINGS",
21
+ "BATTERY_SAVER_SETTINGS",
22
+ "BLUETOOTH_SETTINGS",
23
+ "CAPTIONING_SETTINGS",
24
+ "CAST_SETTINGS",
25
+ "DATA_ROAMING_SETTINGS",
26
+ "DATE_SETTINGS",
27
+ "DEVICE_INFO_SETTINGS",
28
+ "DISPLAY_SETTINGS",
29
+ "DREAM_SETTINGS",
30
+ "HARD_KEYBOARD_SETTINGS",
31
+ "HOME_SETTINGS",
32
+ "IGNORE_BATTERY_OPTIMIZATION_SETTINGS",
33
+ "INPUT_METHOD_SETTINGS",
34
+ "INPUT_METHOD_SUBTYPE_SETTINGS",
35
+ "INTERNAL_STORAGE_SETTINGS",
36
+ "LOCALE_SETTINGS",
37
+ "LOCATION_SOURCE_SETTINGS",
38
+ "MANAGE_ALL_APPLICATIONS_SETTINGS",
39
+ "MANAGE_APPLICATIONS_SETTINGS",
40
+ "MEMORY_CARD_SETTINGS",
41
+ "NETWORK_OPERATOR_SETTINGS",
42
+ "NFC_PAYMENT_SETTINGS",
43
+ "NFC_SETTINGS",
44
+ "NFCSHARING_SETTINGS",
45
+ "NOTIFICATION_SETTINGS",
46
+ "PRIVACY_SETTINGS",
47
+ "QUICK_LAUNCH_SETTINGS",
48
+ "SEARCH_SETTINGS",
49
+ "SECURITY_SETTINGS",
50
+ "SETTINGS",
51
+ "SOUND_SETTINGS",
52
+ "SYNC_SETTINGS",
53
+ "TETHER_SETTINGS",
54
+ "USAGE_ACCESS_SETTINGS",
55
+ "USER_DICTIONARY_SETTINGS",
56
+ "VOICE_INPUT_SETTINGS",
57
+ "VPN_SETTINGS",
58
+ "WIFI_IP_SETTINGS",
59
+ "WIFI_SETTINGS",
60
+ "WIRELESS_SETTINGS",
61
+ ]);
62
+ /**
63
+ * Accepts either the bare suffix (e.g. "WIFI_SETTINGS") or the fully qualified
64
+ * action (e.g. "android.settings.WIFI_SETTINGS") and returns the fully
65
+ * qualified form. Throws FlingError("INVALID_INPUT") on anything else — most
66
+ * importantly, on actions outside the allowlist, which is the guard against
67
+ * using this tool to fire arbitrary intents.
68
+ */
69
+ export function normalizeSettingsAction(input) {
70
+ const trimmed = input.trim();
71
+ if (!trimmed) {
72
+ throw new FlingError("INVALID_INPUT", "Settings action is required.");
73
+ }
74
+ const suffix = trimmed.startsWith(ACTION_PREFIX)
75
+ ? trimmed.slice(ACTION_PREFIX.length)
76
+ : trimmed;
77
+ if (!SUFFIX_RE.test(suffix)) {
78
+ throw new FlingError("INVALID_INPUT", `Invalid settings action: "${input}". Expected an uppercase suffix like WIFI_SETTINGS, optionally prefixed with android.settings..`);
79
+ }
80
+ if (!SETTINGS_ACTION_ALLOWLIST.has(suffix)) {
81
+ throw new FlingError("INVALID_INPUT", `Settings action "${suffix}" is not in the allowlist. Known actions: ${[...SETTINGS_ACTION_ALLOWLIST].sort().join(", ")}.`);
82
+ }
83
+ return ACTION_PREFIX + suffix;
84
+ }
85
+ /**
86
+ * Strictly validates the `-d` payload. The only documented use for a data URI
87
+ * on a built-in settings intent is APPLICATION_DETAILS_SETTINGS, which expects
88
+ * `package:<dotted-android-id>`. Anything else is rejected to prevent arbitrary
89
+ * URI dispatch via this tool.
90
+ */
91
+ export function validateSettingsDataUri(uri) {
92
+ if (!DATA_URI_RE.test(uri)) {
93
+ throw new FlingError("INVALID_INPUT", `Invalid data_uri: "${uri}". Only package:<dotted-android-id> is accepted (e.g. package:com.example.app).`);
94
+ }
95
+ }
96
+ export function buildSettingsAmArgs(params) {
97
+ // -W makes `am start` wait for the activity to come up and print Status: ok;
98
+ // without it the success line is suppressed and we can't tell whether the
99
+ // dispatch actually resolved an activity.
100
+ const argv = [
101
+ ...params.deviceArgs,
102
+ "shell",
103
+ "am",
104
+ "start",
105
+ "-W",
106
+ "-a",
107
+ params.action,
108
+ ];
109
+ if (params.dataUri) {
110
+ argv.push("-d", params.dataUri);
111
+ }
112
+ return argv;
113
+ }
114
+ /**
115
+ * Interpret `am start` output. Mirrors performLaunch in launch-app.ts: the
116
+ * activity manager merges device-side stderr into adb stdout in some Android
117
+ * builds, so we concatenate both and look for known failure markers OR the
118
+ * absence of `Status: ok` (which `am start -W` always emits on success).
119
+ */
120
+ export function interpretSettingsResult(stdout, stderr) {
121
+ const combined = `${stdout}\n${stderr}`;
122
+ const failureRe = /Error: (?:Activity (?:class|not started)|Intent does not match|.+ not found)|java\.lang\.SecurityException|No Activity found to handle/;
123
+ const successRe = /Status:\s*ok/i;
124
+ if (failureRe.test(combined) || !successRe.test(combined)) {
125
+ const reason = combined.trim().split(/\r?\n/).slice(0, 8).join("\n");
126
+ return {
127
+ success: false,
128
+ message: "Settings launch dispatch failed.",
129
+ raw: reason,
130
+ };
131
+ }
132
+ return {
133
+ success: true,
134
+ message: "Settings launch dispatched.",
135
+ raw: combined.trim(),
136
+ };
137
+ }
138
+ export async function performLaunchSettings(params) {
139
+ const { stdout, stderr } = await runAdb(buildSettingsAmArgs(params));
140
+ return interpretSettingsResult(stdout, stderr);
141
+ }
142
+ export function registerLaunchSettings(server) {
143
+ server.registerTool("launch_settings", {
144
+ title: "Launch an Android system settings screen by intent action",
145
+ description: "Open a built-in Android Settings screen via `am start -a android.settings.<ACTION>`. " +
146
+ "Dramatically faster than navigating Settings by screenshot + dump_ui + input_tap " +
147
+ "loops — one adb call instead of ~10 round-trips. " +
148
+ "`action` accepts either the bare suffix (e.g. WIFI_SETTINGS) or the fully qualified " +
149
+ "form (android.settings.WIFI_SETTINGS), restricted to a known-good allowlist of " +
150
+ "standard android.provider.Settings.ACTION_* values. " +
151
+ "`data_uri` is for actions that need a target, currently restricted to " +
152
+ "package:<dotted-android-id> (the canonical form for APPLICATION_DETAILS_SETTINGS). " +
153
+ "For common panels by friendly name (wifi/bluetooth/apps/display/etc.), prefer " +
154
+ "`open_setting` — same intent dispatch with friendlier naming.",
155
+ inputSchema: {
156
+ action: z
157
+ .string()
158
+ .min(1)
159
+ .describe("Settings action. Bare suffix (WIFI_SETTINGS) or fully qualified (android.settings.WIFI_SETTINGS). Must be on the allowlist."),
160
+ data_uri: z
161
+ .string()
162
+ .min(1)
163
+ .optional()
164
+ .describe("Optional `-d` payload. Must be of the form package:<dotted-id>, e.g. package:com.example.app for APPLICATION_DETAILS_SETTINGS."),
165
+ device_id: deviceIdInput,
166
+ },
167
+ outputSchema: {
168
+ device_id: z.string(),
169
+ action: z.string(),
170
+ data_uri: z.string().optional(),
171
+ success: z.boolean(),
172
+ message: z.string(),
173
+ },
174
+ annotations: {
175
+ readOnlyHint: false,
176
+ destructiveHint: false,
177
+ idempotentHint: false,
178
+ openWorldHint: false,
179
+ },
180
+ }, async ({ action, data_uri, device_id }) => {
181
+ try {
182
+ const fullAction = normalizeSettingsAction(action);
183
+ if (data_uri !== undefined)
184
+ validateSettingsDataUri(data_uri);
185
+ const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
186
+ const result = await performLaunchSettings({
187
+ action: fullAction,
188
+ dataUri: data_uri,
189
+ deviceArgs,
190
+ });
191
+ const text = result.success
192
+ ? `${result.message} (${fullAction}) on ${serial}.`
193
+ : `${result.message} (${fullAction} on ${serial})\n\n${result.raw}`;
194
+ return {
195
+ content: [{ type: "text", text }],
196
+ structuredContent: {
197
+ device_id: serial,
198
+ action: fullAction,
199
+ data_uri,
200
+ success: result.success,
201
+ message: text,
202
+ },
203
+ isError: !result.success,
204
+ };
205
+ }
206
+ catch (err) {
207
+ return toolErrorFrom(err);
208
+ }
209
+ });
210
+ }
211
+ //# sourceMappingURL=launch-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-settings.js","sourceRoot":"","sources":["../../src/tools/launch-settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerListDevices(server: McpServer): void;
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { formatDevicesSummary, listDevices } from "../devices.js";
3
+ import { deviceShape } from "../schemas.js";
4
+ import { toolErrorFrom } from "../toolResult.js";
5
+ export function registerListDevices(server) {
6
+ server.registerTool("list_devices", {
7
+ title: "List Android devices",
8
+ description: "Show every Android device adb can see (USB or wireless), with its state " +
9
+ "(device / unauthorized / offline / etc). Use this first to confirm a phone " +
10
+ "is reachable before any other operation.",
11
+ inputSchema: {},
12
+ outputSchema: {
13
+ devices: z.array(deviceShape),
14
+ count: z.number().int().nonnegative(),
15
+ },
16
+ annotations: {
17
+ readOnlyHint: true,
18
+ openWorldHint: false,
19
+ },
20
+ }, async () => {
21
+ try {
22
+ const devices = await listDevices();
23
+ return {
24
+ content: [
25
+ { type: "text", text: formatDevicesSummary(devices) },
26
+ ],
27
+ structuredContent: { devices, count: devices.length },
28
+ };
29
+ }
30
+ catch (err) {
31
+ return toolErrorFrom(err);
32
+ }
33
+ });
34
+ }
35
+ //# sourceMappingURL=list-devices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-devices.js","sourceRoot":"","sources":["../../src/tools/list-devices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EACT,0EAA0E;YAC1E,6EAA6E;YAC7E,0CAA0C;QAC5C,WAAW,EAAE,EAAE;QACf,YAAY,EAAE;YACZ,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC;YAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;SACtC;QACD,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;YACpC,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC,EAAE;iBAC/D;gBACD,iBAAiB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE;aACtD,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function buildLongPressArgs(deviceArgs: string[], x: number, y: number, durationMs: number): string[];
3
+ export declare function registerLongPressByText(server: McpServer): void;
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ import { fetchUiDump } from "../uiDump.js";
3
+ import { runAdb } from "../adb.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
+ import { selectTapTarget } from "./tap-by-text.js";
9
+ export function buildLongPressArgs(deviceArgs, x, y, durationMs) {
10
+ return [
11
+ ...deviceArgs,
12
+ "shell",
13
+ "input",
14
+ "swipe",
15
+ String(x),
16
+ String(y),
17
+ String(x),
18
+ String(y),
19
+ String(durationMs),
20
+ ];
21
+ }
22
+ export function registerLongPressByText(server) {
23
+ server.registerTool("long_press_by_text", {
24
+ title: "Long-press a UI element by visible text",
25
+ description: "Same matching and disambiguation as tap_by_text, but holds the touch for " +
26
+ "the specified duration (default 1000ms). Used to invoke context menus, " +
27
+ "drag handles, and similar long-press affordances.",
28
+ inputSchema: {
29
+ text: z.string().min(1).describe("Visible text to match."),
30
+ exact: z
31
+ .boolean()
32
+ .optional()
33
+ .default(false)
34
+ .describe("Require equality instead of case-sensitive substring."),
35
+ duration_ms: z
36
+ .number()
37
+ .int()
38
+ .positive()
39
+ .max(10_000)
40
+ .optional()
41
+ .default(1000)
42
+ .describe("How long to hold the touch, in milliseconds (1–9999)."),
43
+ device_id: deviceIdInput,
44
+ },
45
+ outputSchema: {
46
+ device_id: z.string(),
47
+ matched_text: z.string(),
48
+ bounds: z.object({
49
+ x1: z.number(),
50
+ y1: z.number(),
51
+ x2: z.number(),
52
+ y2: z.number(),
53
+ }),
54
+ tap_x: z.number(),
55
+ tap_y: z.number(),
56
+ duration_ms: z.number().int().positive(),
57
+ candidates_count: z.number().int().nonnegative(),
58
+ fell_back_to_match: z.boolean(),
59
+ },
60
+ annotations: {
61
+ readOnlyHint: false,
62
+ destructiveHint: false,
63
+ idempotentHint: false,
64
+ openWorldHint: false,
65
+ },
66
+ }, async ({ text, exact, duration_ms, device_id }) => {
67
+ try {
68
+ const { args: deviceArgs, serial } = await resolveDeviceArgs(device_id);
69
+ const { nodes } = await fetchUiDump(deviceArgs);
70
+ const target = selectTapTarget(nodes, {
71
+ by: "text",
72
+ value: text,
73
+ exact,
74
+ });
75
+ if (!target) {
76
+ throw new FlingError("UI_ELEMENT_NOT_FOUND", `No clickable element found for text="${text}" on ${serial}.`);
77
+ }
78
+ await runAdb(buildLongPressArgs(deviceArgs, target.tap_x, target.tap_y, duration_ms));
79
+ const msg = `Long-pressed "${target.matched_text}" for ${duration_ms}ms at (${target.tap_x}, ${target.tap_y}) on ${serial}.`;
80
+ return {
81
+ content: [{ type: "text", text: msg }],
82
+ structuredContent: {
83
+ device_id: serial,
84
+ matched_text: target.matched_text,
85
+ bounds: target.bounds,
86
+ tap_x: target.tap_x,
87
+ tap_y: target.tap_y,
88
+ duration_ms,
89
+ candidates_count: target.candidates_count,
90
+ fell_back_to_match: target.fell_back_to_match,
91
+ },
92
+ };
93
+ }
94
+ catch (err) {
95
+ return toolErrorFrom(err);
96
+ }
97
+ });
98
+ }
99
+ //# sourceMappingURL=long-press-by-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"long-press-by-text.js","sourceRoot":"","sources":["../../src/tools/long-press-by-text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const SETTINGS_ACTION_ALLOWLIST: ReadonlySet<string>;
3
+ export declare function panelToAction(panel: string): string;
4
+ /**
5
+ * Accepts either the bare suffix (e.g. "WIFI_SETTINGS") or the fully qualified
6
+ * action (e.g. "android.settings.WIFI_SETTINGS") and returns the fully
7
+ * qualified form. Throws FlingError("INVALID_INPUT") on anything else — most
8
+ * importantly, on actions outside the allowlist, which is the guard against
9
+ * using this tool to fire arbitrary intents.
10
+ */
11
+ export declare function normalizeSettingsAction(input: string): string;
12
+ /**
13
+ * Strictly validates the `-d` payload. The only documented use for a data URI
14
+ * on a built-in settings intent is APPLICATION_DETAILS_SETTINGS, which expects
15
+ * `package:<dotted-android-id>`. Anything else is rejected to prevent arbitrary
16
+ * URI dispatch via this tool.
17
+ */
18
+ export declare function validateSettingsDataUri(uri: string): void;
19
+ export interface BuildSettingsAmArgsParams {
20
+ action: string;
21
+ dataUri?: string;
22
+ deviceArgs: string[];
23
+ }
24
+ export declare function buildSettingsAmArgs(params: BuildSettingsAmArgsParams): string[];
25
+ export interface SettingsLaunchResult {
26
+ success: boolean;
27
+ message: string;
28
+ raw: string;
29
+ }
30
+ /**
31
+ * Interpret `am start` output. The activity manager merges device-side stderr
32
+ * into adb stdout in some Android builds, so we concatenate both and look for
33
+ * known failure markers OR the absence of `Status: ok` (which `am start -W`
34
+ * always emits on success).
35
+ */
36
+ export declare function interpretSettingsResult(stdout: string, stderr: string): SettingsLaunchResult;
37
+ export interface PerformOpenSettingParams {
38
+ action: string;
39
+ dataUri?: string;
40
+ deviceArgs: string[];
41
+ }
42
+ export declare function performOpenSetting(params: PerformOpenSettingParams): Promise<SettingsLaunchResult>;
43
+ export interface ResolveOpenSettingActionInput {
44
+ panel?: string;
45
+ action?: string;
46
+ }
47
+ /**
48
+ * Resolve the user's panel-or-action input into the fully qualified
49
+ * android.settings.* action string. Enforces XOR — exactly one of
50
+ * {panel, action} must be set. Delegates value validation to panelToAction
51
+ * (unknown panel) and normalizeSettingsAction (unknown / malformed /
52
+ * non-allowlisted action), each of which throws INVALID_INPUT on rejection.
53
+ */
54
+ export declare function resolveOpenSettingAction(input: ResolveOpenSettingActionInput): string;
55
+ export declare function registerOpenSetting(server: McpServer): void;