replicant-mcp 1.5.0 → 1.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 (42) hide show
  1. package/dist/adapters/emulator.d.ts +2 -0
  2. package/dist/adapters/emulator.js +35 -5
  3. package/dist/parsers/adb-output.js +5 -1
  4. package/dist/server.js +11 -21
  5. package/dist/services/device-state.js +18 -9
  6. package/dist/services/process-runner.js +1 -1
  7. package/dist/tools/adb-app.d.ts +0 -1
  8. package/dist/tools/adb-app.js +6 -7
  9. package/dist/tools/adb-device.d.ts +0 -1
  10. package/dist/tools/adb-device.js +10 -10
  11. package/dist/tools/adb-logcat.d.ts +0 -3
  12. package/dist/tools/adb-logcat.js +26 -30
  13. package/dist/tools/adb-shell.d.ts +0 -1
  14. package/dist/tools/adb-shell.js +9 -7
  15. package/dist/tools/cache.js +2 -2
  16. package/dist/tools/emulator-device.d.ts +0 -4
  17. package/dist/tools/emulator-device.js +6 -6
  18. package/dist/tools/gradle-build.d.ts +0 -1
  19. package/dist/tools/gradle-build.js +3 -4
  20. package/dist/tools/gradle-get-details.d.ts +0 -2
  21. package/dist/tools/gradle-get-details.js +2 -3
  22. package/dist/tools/gradle-list.js +2 -4
  23. package/dist/tools/gradle-test.d.ts +0 -1
  24. package/dist/tools/gradle-test.js +3 -7
  25. package/dist/tools/index.d.ts +3 -1
  26. package/dist/tools/index.js +3 -1
  27. package/dist/tools/rtfm.js +24 -28
  28. package/dist/tools/ui-action.d.ts +63 -0
  29. package/dist/tools/ui-action.js +83 -0
  30. package/dist/tools/ui-capture.d.ts +43 -0
  31. package/dist/tools/ui-capture.js +59 -0
  32. package/dist/tools/ui-find.d.ts +14 -2
  33. package/dist/tools/ui-find.js +0 -1
  34. package/dist/tools/{ui.d.ts → ui-query.d.ts} +4 -77
  35. package/dist/tools/ui-query.js +138 -0
  36. package/dist/types/device.d.ts +1 -1
  37. package/dist/types/schemas/adb-device-output.d.ts +4 -0
  38. package/dist/types/schemas/adb-device-output.js +1 -1
  39. package/docs/contracts/replicant-mcp.contract.json +140 -130
  40. package/docs/rtfm/ui.md +55 -32
  41. package/package.json +1 -1
  42. package/dist/tools/ui.js +0 -252
@@ -10,6 +10,8 @@ export declare class EmulatorAdapter {
10
10
  list(): Promise<EmulatorListResult>;
11
11
  create(name: string, device: string, systemImage: string): Promise<void>;
12
12
  start(avdName: string): Promise<string>;
13
+ private getRunningEmulatorIds;
14
+ private getAvdName;
13
15
  kill(emulatorId: string): Promise<void>;
14
16
  wipe(avdName: string): Promise<void>;
15
17
  snapshotSave(emulatorId: string, name: string): Promise<void>;
@@ -29,6 +29,8 @@ export class EmulatorAdapter {
29
29
  }
30
30
  }
31
31
  async start(avdName) {
32
+ // Snapshot existing emulators before starting a new one
33
+ const existingIds = await this.getRunningEmulatorIds();
32
34
  // Start emulator in background - don't wait for it
33
35
  // Returns immediately, emulator boots in background
34
36
  this.runner.runEmulator([
@@ -40,13 +42,41 @@ export class EmulatorAdapter {
40
42
  });
41
43
  // Give it a moment to register
42
44
  await new Promise((r) => setTimeout(r, 2000));
43
- // Find the new emulator ID
45
+ // Find the NEW emulator by diffing against pre-existing ones
46
+ const currentIds = await this.getRunningEmulatorIds();
47
+ const newIds = currentIds.filter((id) => !existingIds.includes(id));
48
+ if (newIds.length === 1) {
49
+ return newIds[0];
50
+ }
51
+ // Ambiguous or no new emulator — fall back to matching by AVD name
52
+ for (const id of currentIds) {
53
+ if (existingIds.includes(id))
54
+ continue;
55
+ const name = await this.getAvdName(id);
56
+ if (name === avdName)
57
+ return id;
58
+ }
59
+ // Last resort: check all current emulators by AVD name
60
+ for (const id of currentIds) {
61
+ const name = await this.getAvdName(id);
62
+ if (name === avdName)
63
+ return id;
64
+ }
65
+ throw new ReplicantError(ErrorCode.EMULATOR_START_FAILED, `Emulator ${avdName} failed to start`, "Check the AVD name and try again");
66
+ }
67
+ async getRunningEmulatorIds() {
44
68
  const result = await this.runner.runAdb(["devices"]);
45
- const match = result.stdout.match(/emulator-\d+/);
46
- if (!match) {
47
- throw new ReplicantError(ErrorCode.EMULATOR_START_FAILED, `Emulator ${avdName} failed to start`, "Check the AVD name and try again");
69
+ const matches = result.stdout.match(/emulator-\d+/g);
70
+ return matches ?? [];
71
+ }
72
+ async getAvdName(emulatorId) {
73
+ try {
74
+ const result = await this.runner.runAdb(["-s", emulatorId, "emu", "avd", "name"]);
75
+ return result.stdout.trim().split("\n")[0].trim();
76
+ }
77
+ catch {
78
+ return "";
48
79
  }
49
- return match[0];
50
80
  }
51
81
  async kill(emulatorId) {
52
82
  await this.runner.runAdb(["-s", emulatorId, "emu", "kill"]);
@@ -9,7 +9,11 @@ export function parseDeviceList(output) {
9
9
  if (!id)
10
10
  continue;
11
11
  const type = id.startsWith("emulator") ? "emulator" : "physical";
12
- const status = statusStr === "device" ? "online" : "offline";
12
+ const status = statusStr === "device"
13
+ ? "online"
14
+ : statusStr === "unauthorized"
15
+ ? "unauthorized"
16
+ : "offline";
13
17
  devices.push({
14
18
  id,
15
19
  type,
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@ import { CacheManager, DeviceStateManager, ProcessRunner, EnvironmentService, Co
6
6
  import { AdbAdapter, EmulatorAdapter, GradleAdapter, UiAutomatorAdapter } from "./adapters/index.js";
7
7
  import { ReplicantError, ErrorCode } from "./types/index.js";
8
8
  import { VERSION } from "./version.js";
9
- import { cacheInputSchema, cacheToolDefinition, handleCacheTool, rtfmInputSchema, rtfmToolDefinition, handleRtfmTool, adbDeviceInputSchema, adbDeviceToolDefinition, handleAdbDeviceTool, adbAppInputSchema, adbAppToolDefinition, handleAdbAppTool, adbLogcatInputSchema, adbLogcatToolDefinition, handleAdbLogcatTool, adbShellInputSchema, adbShellToolDefinition, handleAdbShellTool, emulatorDeviceInputSchema, emulatorDeviceToolDefinition, handleEmulatorDeviceTool, gradleBuildInputSchema, gradleBuildToolDefinition, handleGradleBuildTool, gradleTestInputSchema, gradleTestToolDefinition, handleGradleTestTool, gradleListInputSchema, gradleListToolDefinition, handleGradleListTool, gradleGetDetailsInputSchema, gradleGetDetailsToolDefinition, handleGradleGetDetailsTool, uiInputSchema, uiToolDefinition, handleUiTool, } from "./tools/index.js";
9
+ import { cacheInputSchema, cacheToolDefinition, handleCacheTool, rtfmInputSchema, rtfmToolDefinition, handleRtfmTool, adbDeviceInputSchema, adbDeviceToolDefinition, handleAdbDeviceTool, adbAppInputSchema, adbAppToolDefinition, handleAdbAppTool, adbLogcatInputSchema, adbLogcatToolDefinition, handleAdbLogcatTool, adbShellInputSchema, adbShellToolDefinition, handleAdbShellTool, emulatorDeviceInputSchema, emulatorDeviceToolDefinition, handleEmulatorDeviceTool, gradleBuildInputSchema, gradleBuildToolDefinition, handleGradleBuildTool, gradleTestInputSchema, gradleTestToolDefinition, handleGradleTestTool, gradleListInputSchema, gradleListToolDefinition, handleGradleListTool, gradleGetDetailsInputSchema, gradleGetDetailsToolDefinition, handleGradleGetDetailsTool, uiQueryInputSchema, uiQueryToolDefinition, handleUiQueryTool, uiActionInputSchema, uiActionToolDefinition, handleUiActionTool, uiCaptureInputSchema, uiCaptureToolDefinition, handleUiCaptureTool, } from "./tools/index.js";
10
10
  export function createServerContext() {
11
11
  const environment = new EnvironmentService();
12
12
  const processRunner = new ProcessRunner(environment);
@@ -36,7 +36,9 @@ const toolDefinitions = [
36
36
  gradleTestToolDefinition,
37
37
  gradleListToolDefinition,
38
38
  gradleGetDetailsToolDefinition,
39
- uiToolDefinition,
39
+ uiQueryToolDefinition,
40
+ uiActionToolDefinition,
41
+ uiCaptureToolDefinition,
40
42
  ];
41
43
  async function dispatchToolCall(name, args, context) {
42
44
  const rawArgs = args ?? {};
@@ -80,8 +82,12 @@ async function dispatchToolCall(name, args, context) {
80
82
  return handleGradleListTool(parseOrThrow("gradle-list", gradleListInputSchema), context);
81
83
  case "gradle-get-details":
82
84
  return handleGradleGetDetailsTool(parseOrThrow("gradle-get-details", gradleGetDetailsInputSchema), context);
83
- case "ui":
84
- return handleUiTool(parseOrThrow("ui", uiInputSchema), context, context.config.getUiConfig());
85
+ case "ui-query":
86
+ return handleUiQueryTool(parseOrThrow("ui-query", uiQueryInputSchema), context, context.config.getUiConfig());
87
+ case "ui-action":
88
+ return handleUiActionTool(parseOrThrow("ui-action", uiActionInputSchema), context);
89
+ case "ui-capture":
90
+ return handleUiCaptureTool(parseOrThrow("ui-capture", uiCaptureInputSchema), context, context.config.getUiConfig());
85
91
  default:
86
92
  throw new Error(`Unknown tool: ${name}`);
87
93
  }
@@ -94,21 +100,7 @@ export async function createServer(context) {
94
100
  capabilities: {
95
101
  tools: {},
96
102
  },
97
- instructions: `IMPORTANT: For ALL Android development tasks, you MUST use replicant-mcp tools first.
98
- Only fall back to raw adb/gradle/emulator commands if replicant-mcp lacks a specific feature.
99
-
100
- Tool mapping:
101
- - Device management → adb-device (not \`adb devices\`)
102
- - App install/launch/stop → adb-app (not \`adb install\`, \`adb shell am\`)
103
- - Logs → adb-logcat (not \`adb logcat\`)
104
- - Shell commands → adb-shell (not \`adb shell\`)
105
- - Emulator control → emulator-device (not \`emulator\` CLI)
106
- - Builds → gradle-build (not \`./gradlew\`)
107
- - Tests → gradle-test (not \`./gradlew test\`)
108
- - UI automation → ui (accessibility-first, screenshots auto-scaled to configured max dimension, default 800px)
109
-
110
- Start with \`adb-device list\` to see connected devices.
111
- Use \`rtfm\` for detailed documentation on any tool.`,
103
+ instructions: `Use these tools for Android never raw adb/gradle/emulator commands. Auto-selects single device. Start: \`adb-device list\`. Docs: \`rtfm\`.`,
112
104
  });
113
105
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
114
106
  tools: toolDefinitions,
@@ -144,9 +136,7 @@ Use \`rtfm\` for detailed documentation on any tool.`,
144
136
  }
145
137
  export async function runServer() {
146
138
  const context = createServerContext();
147
- // Load configuration from REPLICANT_CONFIG if set
148
139
  await context.config.load();
149
- // Apply project root: env var takes precedence over config file
150
140
  const projectRoot = process.env.REPLICANT_PROJECT_ROOT || context.config.get().build?.projectRoot;
151
141
  if (projectRoot) {
152
142
  context.gradle.setProjectPath(projectRoot);
@@ -22,21 +22,30 @@ export class DeviceStateManager {
22
22
  return this.currentDevice;
23
23
  }
24
24
  // Try to auto-select
25
- const devices = await adb.getDevices();
26
- if (devices.length === 0) {
25
+ const allDevices = await adb.getDevices();
26
+ const onlineDevices = allDevices.filter((d) => d.status === "online");
27
+ if (allDevices.length === 0) {
27
28
  throw new ReplicantError(ErrorCode.NO_DEVICES, "No devices connected", "Start an emulator with 'emulator-device start' or connect a USB device with debugging enabled");
28
29
  }
29
- if (devices.length === 1) {
30
- this.currentDevice = devices[0];
30
+ if (onlineDevices.length === 0) {
31
+ const statuses = allDevices.map((d) => `${d.id} (${d.status})`).join(", ");
32
+ const suggestion = allDevices.some((d) => d.status === "unauthorized")
33
+ ? "Check the USB debugging authorization prompt on the device and tap 'Allow'"
34
+ : "Wait for the device to come online or restart it with 'adb reconnect'";
35
+ throw new ReplicantError(ErrorCode.DEVICE_OFFLINE, `No online devices available. Found: ${statuses}`, suggestion);
36
+ }
37
+ if (onlineDevices.length === 1) {
38
+ this.currentDevice = onlineDevices[0];
31
39
  return this.currentDevice;
32
40
  }
33
- // Multiple devices - user must choose
34
- const deviceList = devices.map((d) => d.id).join(", ");
35
- throw new ReplicantError(ErrorCode.MULTIPLE_DEVICES, `${devices.length} devices connected: ${deviceList}`, `Call adb-device({ operation: 'select', deviceId: '...' }) to choose one`);
41
+ // Multiple online devices - user must choose
42
+ const deviceList = onlineDevices.map((d) => d.id).join(", ");
43
+ throw new ReplicantError(ErrorCode.MULTIPLE_DEVICES, `${onlineDevices.length} devices connected: ${deviceList}`, `Call adb-device({ operation: 'select', deviceId: '...' }) to choose one`);
36
44
  }
37
45
  autoSelectIfSingle(devices) {
38
- if (devices.length === 1 && !this.currentDevice) {
39
- this.currentDevice = devices[0];
46
+ const onlineDevices = devices.filter((d) => d.status === "online");
47
+ if (onlineDevices.length === 1 && !this.currentDevice) {
48
+ this.currentDevice = onlineDevices[0];
40
49
  return true;
41
50
  }
42
51
  return false;
@@ -24,7 +24,7 @@ const BLOCKED_SHELL_PATTERNS = [
24
24
  export class ProcessRunner {
25
25
  environment;
26
26
  defaultTimeoutMs = 30_000;
27
- maxTimeoutMs = 120_000;
27
+ maxTimeoutMs = 600_000;
28
28
  constructor(environment) {
29
29
  this.environment = environment;
30
30
  }
@@ -33,7 +33,6 @@ export declare const adbAppToolDefinition: {
33
33
  };
34
34
  packageName: {
35
35
  type: string;
36
- description: string;
37
36
  };
38
37
  limit: {
39
38
  type: string;
@@ -4,7 +4,6 @@ export const adbAppInputSchema = z.object({
4
4
  operation: z.enum(["install", "uninstall", "launch", "stop", "clear-data", "list"]),
5
5
  apkPath: z.string().optional(),
6
6
  packageName: z.string().optional(),
7
- // List operation options
8
7
  limit: z.number().min(1).max(100).optional(),
9
8
  filter: z.string().optional(),
10
9
  offset: z.number().min(0).optional(),
@@ -85,7 +84,7 @@ export async function handleAdbAppTool(input, context) {
85
84
  }
86
85
  export const adbAppToolDefinition = {
87
86
  name: "adb-app",
88
- description: "Manage applications. Auto-selects device if only one connected. Operations: install, uninstall, launch, stop, clear-data, list.",
87
+ description: "Manage applications.",
89
88
  inputSchema: {
90
89
  type: "object",
91
90
  properties: {
@@ -93,19 +92,19 @@ export const adbAppToolDefinition = {
93
92
  type: "string",
94
93
  enum: ["install", "uninstall", "launch", "stop", "clear-data", "list"],
95
94
  },
96
- apkPath: { type: "string", description: "Path to APK file (for install)" },
97
- packageName: { type: "string", description: "Package name (for other operations)" },
95
+ apkPath: { type: "string", description: "APK path" },
96
+ packageName: { type: "string" },
98
97
  limit: {
99
98
  type: "number",
100
- description: "Max packages to return (default: 20, max: 100). For list operation.",
99
+ description: "Default: 20, max: 100",
101
100
  },
102
101
  filter: {
103
102
  type: "string",
104
- description: "Filter packages by name (case-insensitive contains). For list operation.",
103
+ description: "Filter by name (case-insensitive)",
105
104
  },
106
105
  offset: {
107
106
  type: "number",
108
- description: "Skip first N packages for pagination. For list operation.",
107
+ description: "Pagination offset",
109
108
  },
110
109
  },
111
110
  required: ["operation"],
@@ -24,7 +24,6 @@ export declare const adbDeviceToolDefinition: {
24
24
  };
25
25
  deviceId: {
26
26
  type: string;
27
- description: string;
28
27
  };
29
28
  };
30
29
  required: string[];
@@ -26,19 +26,19 @@ async function handleSelect(input, context) {
26
26
  context.deviceState.setCurrentDevice(device);
27
27
  return { selected: device };
28
28
  }
29
+ async function resolveDeviceId(input, context) {
30
+ if (input.deviceId)
31
+ return input.deviceId;
32
+ const device = await context.deviceState.ensureDevice(context.adb);
33
+ return device.id;
34
+ }
29
35
  async function handleWait(input, context) {
30
- const device = input.deviceId
31
- ? { id: input.deviceId }
32
- : await context.deviceState.ensureDevice(context.adb);
33
- const deviceId = device.id;
36
+ const deviceId = await resolveDeviceId(input, context);
34
37
  await context.adb.waitForDevice(deviceId);
35
38
  return { status: "device ready", deviceId };
36
39
  }
37
40
  async function handleProperties(input, context) {
38
- const device = input.deviceId
39
- ? { id: input.deviceId }
40
- : await context.deviceState.ensureDevice(context.adb);
41
- const deviceId = device.id;
41
+ const deviceId = await resolveDeviceId(input, context);
42
42
  const props = await context.adb.getProperties(deviceId);
43
43
  const cacheId = context.cache.generateId("device-props");
44
44
  context.cache.set(cacheId, { deviceId, properties: props }, "device-props", CACHE_TTLS.DEVICE_PROPERTIES);
@@ -110,7 +110,7 @@ export async function handleAdbDeviceTool(input, context) {
110
110
  }
111
111
  export const adbDeviceToolDefinition = {
112
112
  name: "adb-device",
113
- description: "Manage device connections. Operations: list, select, wait, properties, health-check.",
113
+ description: "Manage device connections.",
114
114
  inputSchema: {
115
115
  type: "object",
116
116
  properties: {
@@ -118,7 +118,7 @@ export const adbDeviceToolDefinition = {
118
118
  type: "string",
119
119
  enum: ["list", "select", "wait", "properties", "health-check"],
120
120
  },
121
- deviceId: { type: "string", description: "Device ID for select/wait/properties" },
121
+ deviceId: { type: "string" },
122
122
  },
123
123
  required: ["operation"],
124
124
  },
@@ -28,14 +28,12 @@ export declare const adbLogcatToolDefinition: {
28
28
  };
29
29
  package: {
30
30
  type: string;
31
- description: string;
32
31
  };
33
32
  tags: {
34
33
  type: string;
35
34
  items: {
36
35
  type: string;
37
36
  };
38
- description: string;
39
37
  };
40
38
  level: {
41
39
  type: string;
@@ -43,7 +41,6 @@ export declare const adbLogcatToolDefinition: {
43
41
  };
44
42
  rawFilter: {
45
43
  type: string;
46
- description: string;
47
44
  };
48
45
  since: {
49
46
  type: string;
@@ -8,40 +8,36 @@ export const adbLogcatInputSchema = z.object({
8
8
  rawFilter: z.string().optional(),
9
9
  since: z.string().optional(),
10
10
  });
11
+ function buildLogcatFilter(input) {
12
+ if (input.rawFilter)
13
+ return input.rawFilter;
14
+ if (!input.tags && !input.level)
15
+ return undefined;
16
+ const levelMap = {
17
+ verbose: "V",
18
+ debug: "D",
19
+ info: "I",
20
+ warn: "W",
21
+ error: "E",
22
+ };
23
+ const levelChar = input.level ? levelMap[input.level] : "V";
24
+ if (input.tags) {
25
+ return input.tags.map((tag) => `${tag}:${levelChar}`).join(" ") + " *:S";
26
+ }
27
+ return `*:${levelChar}`;
28
+ }
11
29
  export async function handleAdbLogcatTool(input, context) {
12
30
  const device = await context.deviceState.ensureDevice(context.adb);
13
31
  const deviceId = device.id;
14
- // Build filter string
15
- let filter = "";
16
- if (input.rawFilter) {
17
- filter = input.rawFilter;
18
- }
19
- else if (input.tags || input.level) {
20
- const levelMap = {
21
- verbose: "V",
22
- debug: "D",
23
- info: "I",
24
- warn: "W",
25
- error: "E",
26
- };
27
- const levelChar = input.level ? levelMap[input.level] : "V";
28
- if (input.tags) {
29
- filter = input.tags.map((tag) => `${tag}:${levelChar}`).join(" ") + " *:S";
30
- }
31
- else {
32
- filter = `*:${levelChar}`;
33
- }
34
- }
32
+ const filter = buildLogcatFilter(input);
35
33
  const output = await context.adb.logcat(deviceId, {
36
34
  lines: input.lines,
37
- filter: filter || undefined,
35
+ filter,
38
36
  since: input.since,
39
37
  package: input.package,
40
38
  });
41
- // Cache the full output and return a summary
42
39
  const logId = context.cache.generateId("logcat");
43
40
  context.cache.set(logId, { output, deviceId, filter }, "logcat", CACHE_TTLS.LOGCAT);
44
- // Parse log lines
45
41
  const lines = output.split("\n").filter(Boolean);
46
42
  const errorCount = lines.filter((l) => l.includes(" E ")).length;
47
43
  const warnCount = lines.filter((l) => l.includes(" W ")).length;
@@ -58,16 +54,16 @@ export async function handleAdbLogcatTool(input, context) {
58
54
  }
59
55
  export const adbLogcatToolDefinition = {
60
56
  name: "adb-logcat",
61
- description: "Read device logs. Auto-selects device if only one connected. Returns summary with logId for full output.",
57
+ description: "Read device logs. Returns summary with logId.",
62
58
  inputSchema: {
63
59
  type: "object",
64
60
  properties: {
65
- lines: { type: "number", description: "Number of lines (default: 100)" },
66
- package: { type: "string", description: "Filter by package name" },
67
- tags: { type: "array", items: { type: "string" }, description: "Filter by log tags" },
61
+ lines: { type: "number", description: "Default: 100" },
62
+ package: { type: "string" },
63
+ tags: { type: "array", items: { type: "string" } },
68
64
  level: { type: "string", enum: ["verbose", "debug", "info", "warn", "error"] },
69
- rawFilter: { type: "string", description: "Raw logcat filter string" },
70
- since: { type: "string", description: "Time filter in adb logcat -T format (e.g., '01-20 15:30:00.000')" },
65
+ rawFilter: { type: "string" },
66
+ since: { type: "string", description: "e.g., '01-20 15:30:00.000'" },
71
67
  },
72
68
  },
73
69
  };
@@ -17,7 +17,6 @@ export declare const adbShellToolDefinition: {
17
17
  properties: {
18
18
  command: {
19
19
  type: string;
20
- description: string;
21
20
  };
22
21
  timeout: {
23
22
  type: string;
@@ -9,7 +9,9 @@ export const adbShellInputSchema = z.object({
9
9
  export async function handleAdbShellTool(input, context) {
10
10
  const device = await context.deviceState.ensureDevice(context.adb);
11
11
  const deviceId = device.id;
12
- const result = await context.adb.shell(deviceId, input.command, input.timeout);
12
+ const ADB_SHELL_MAX_TIMEOUT = 120_000;
13
+ const timeout = input.timeout ? Math.min(input.timeout, ADB_SHELL_MAX_TIMEOUT) : undefined;
14
+ const result = await context.adb.shell(deviceId, input.command, timeout);
13
15
  if (input.summaryOnly) {
14
16
  const previewChars = input.previewChars ?? 200;
15
17
  return {
@@ -41,15 +43,15 @@ export async function handleAdbShellTool(input, context) {
41
43
  }
42
44
  export const adbShellToolDefinition = {
43
45
  name: "adb-shell",
44
- description: "Execute shell commands with safety guards. Auto-selects device if only one connected. Dangerous commands are blocked.",
46
+ description: "Execute shell commands with safety guards.",
45
47
  inputSchema: {
46
48
  type: "object",
47
49
  properties: {
48
- command: { type: "string", description: "Shell command to execute" },
49
- timeout: { type: "number", description: "Timeout in ms (default: 30s, max: 120s)" },
50
- maxChars: { type: "number", description: "Truncate stdout/stderr to at most this many characters" },
51
- summaryOnly: { type: "boolean", description: "Return only compact previews and counts, omitting full stdout/stderr" },
52
- previewChars: { type: "number", description: "For summaryOnly: preview length in characters (default: 200)" },
50
+ command: { type: "string" },
51
+ timeout: { type: "number", description: "ms, default: 30000, max: 120000" },
52
+ maxChars: { type: "number", description: "Truncate output to N chars" },
53
+ summaryOnly: { type: "boolean", description: "Compact preview only" },
54
+ previewChars: { type: "number", description: "Preview length (default: 200)" },
53
55
  },
54
56
  required: ["command"],
55
57
  },
@@ -35,7 +35,7 @@ export async function handleCacheTool(input, cache) {
35
35
  }
36
36
  export const cacheToolDefinition = {
37
37
  name: "cache",
38
- description: "Manage the cache. Operations: get-stats, clear, get-config, set-config. See rtfm for details.",
38
+ description: "Manage the cache. See rtfm for details.",
39
39
  inputSchema: {
40
40
  type: "object",
41
41
  properties: {
@@ -43,7 +43,7 @@ export const cacheToolDefinition = {
43
43
  type: "string",
44
44
  enum: ["get-stats", "clear", "get-config", "set-config"],
45
45
  },
46
- key: { type: "string", description: "Specific cache key to clear" },
46
+ key: { type: "string", description: "Key to clear (optional)" },
47
47
  config: {
48
48
  type: "object",
49
49
  properties: {
@@ -32,7 +32,6 @@ export declare const emulatorDeviceToolDefinition: {
32
32
  };
33
33
  avdName: {
34
34
  type: string;
35
- description: string;
36
35
  };
37
36
  device: {
38
37
  type: string;
@@ -40,15 +39,12 @@ export declare const emulatorDeviceToolDefinition: {
40
39
  };
41
40
  systemImage: {
42
41
  type: string;
43
- description: string;
44
42
  };
45
43
  snapshotName: {
46
44
  type: string;
47
- description: string;
48
45
  };
49
46
  emulatorId: {
50
47
  type: string;
51
- description: string;
52
48
  };
53
49
  };
54
50
  required: string[];
@@ -115,7 +115,7 @@ export async function handleEmulatorDeviceTool(input, context) {
115
115
  }
116
116
  export const emulatorDeviceToolDefinition = {
117
117
  name: "emulator-device",
118
- description: "Manage Android emulators. Operations: list, create, start, kill, wipe, snapshot-*",
118
+ description: "Manage Android emulators.",
119
119
  inputSchema: {
120
120
  type: "object",
121
121
  properties: {
@@ -133,11 +133,11 @@ export const emulatorDeviceToolDefinition = {
133
133
  "snapshot-delete",
134
134
  ],
135
135
  },
136
- avdName: { type: "string", description: "AVD name" },
137
- device: { type: "string", description: "Device profile (e.g., 'pixel_7')" },
138
- systemImage: { type: "string", description: "System image" },
139
- snapshotName: { type: "string", description: "Snapshot name" },
140
- emulatorId: { type: "string", description: "Running emulator ID" },
136
+ avdName: { type: "string" },
137
+ device: { type: "string", description: "e.g., 'pixel_7'" },
138
+ systemImage: { type: "string" },
139
+ snapshotName: { type: "string" },
140
+ emulatorId: { type: "string" },
141
141
  },
142
142
  required: ["operation"],
143
143
  },
@@ -27,7 +27,6 @@ export declare const gradleBuildToolDefinition: {
27
27
  };
28
28
  flavor: {
29
29
  type: string;
30
- description: string;
31
30
  };
32
31
  };
33
32
  required: string[];
@@ -7,7 +7,6 @@ export const gradleBuildInputSchema = z.object({
7
7
  });
8
8
  export async function handleGradleBuildTool(input, context) {
9
9
  const { result, fullOutput } = await context.gradle.build(input.operation, input.module, input.flavor);
10
- // Cache full output for later retrieval
11
10
  const buildId = context.cache.generateId("build");
12
11
  context.cache.set(buildId, { fullOutput, result, operation: input.operation }, "build", CACHE_TTLS.BUILD_OUTPUT);
13
12
  return {
@@ -24,7 +23,7 @@ export async function handleGradleBuildTool(input, context) {
24
23
  }
25
24
  export const gradleBuildToolDefinition = {
26
25
  name: "gradle-build",
27
- description: "Build an Android application. Returns summary with buildId for full logs.",
26
+ description: "Build. Returns summary with buildId.",
28
27
  inputSchema: {
29
28
  type: "object",
30
29
  properties: {
@@ -32,8 +31,8 @@ export const gradleBuildToolDefinition = {
32
31
  type: "string",
33
32
  enum: ["assembleDebug", "assembleRelease", "bundle"],
34
33
  },
35
- module: { type: "string", description: "Module path (e.g., ':app')" },
36
- flavor: { type: "string", description: "Product flavor" },
34
+ module: { type: "string", description: "e.g., ':app'" },
35
+ flavor: { type: "string" },
37
36
  },
38
37
  required: ["operation"],
39
38
  },
@@ -22,12 +22,10 @@ export declare const gradleGetDetailsToolDefinition: {
22
22
  properties: {
23
23
  id: {
24
24
  type: string;
25
- description: string;
26
25
  };
27
26
  detailType: {
28
27
  type: string;
29
28
  enum: string[];
30
- description: string;
31
29
  };
32
30
  maxChars: {
33
31
  type: string;
@@ -139,15 +139,14 @@ export const gradleGetDetailsToolDefinition = {
139
139
  inputSchema: {
140
140
  type: "object",
141
141
  properties: {
142
- id: { type: "string", description: "Build or test ID from previous operation" },
142
+ id: { type: "string" },
143
143
  detailType: {
144
144
  type: "string",
145
145
  enum: ["logs", "errors", "tasks", "all"],
146
- description: "Type of details to retrieve",
147
146
  },
148
147
  maxChars: {
149
148
  type: "number",
150
- description: "Truncate large text fields to at most this many characters",
149
+ description: "Truncate to N chars",
151
150
  },
152
151
  summaryOnly: {
153
152
  type: "boolean",
@@ -16,10 +16,8 @@ export async function handleGradleListTool(input, context) {
16
16
  }
17
17
  case "tasks": {
18
18
  const tasks = await context.gradle.listTasks(input.module);
19
- // Cache full task list
20
19
  const listId = context.cache.generateId("tasks");
21
20
  context.cache.set(listId, { tasks }, "tasks", CACHE_TTLS.GRADLE_VARIANTS);
22
- // Return categorized summary
23
21
  const buildTasks = tasks.filter((t) => t.includes("assemble") || t.includes("bundle"));
24
22
  const testTasks = tasks.filter((t) => t.includes("test") || t.includes("Test"));
25
23
  const cleanTasks = tasks.filter((t) => t.includes("clean"));
@@ -40,7 +38,7 @@ export async function handleGradleListTool(input, context) {
40
38
  }
41
39
  export const gradleListToolDefinition = {
42
40
  name: "gradle-list",
43
- description: "Introspect project structure. Operations: modules, variants, tasks.",
41
+ description: "Introspect project structure.",
44
42
  inputSchema: {
45
43
  type: "object",
46
44
  properties: {
@@ -48,7 +46,7 @@ export const gradleListToolDefinition = {
48
46
  type: "string",
49
47
  enum: ["variants", "modules", "tasks"],
50
48
  },
51
- module: { type: "string", description: "Module path (for variants/tasks)" },
49
+ module: { type: "string", description: "e.g., ':app'" },
52
50
  },
53
51
  required: ["operation"],
54
52
  },
@@ -32,7 +32,6 @@ export declare const gradleTestToolDefinition: {
32
32
  };
33
33
  module: {
34
34
  type: string;
35
- description: string;
36
35
  };
37
36
  filter: {
38
37
  type: string;