replicant-mcp 1.6.2 → 1.6.4

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 (47) hide show
  1. package/dist/adapters/ui-automator.d.ts +6 -6
  2. package/dist/adapters/ui-automator.js +19 -31
  3. package/dist/adapters/ui-fallback-find.js +44 -4
  4. package/dist/parsers/ui-dump.d.ts +1 -0
  5. package/dist/parsers/ui-dump.js +34 -2
  6. package/dist/schemas/derive.d.ts +8 -0
  7. package/dist/schemas/derive.js +19 -0
  8. package/dist/schemas/inputs.d.ts +9 -0
  9. package/dist/schemas/inputs.js +44 -0
  10. package/dist/server.js +3 -18
  11. package/dist/tools/adb-app.d.ts +6 -30
  12. package/dist/tools/adb-app.js +10 -29
  13. package/dist/tools/adb-device.d.ts +4 -12
  14. package/dist/tools/adb-device.js +4 -12
  15. package/dist/tools/adb-logcat.d.ts +5 -29
  16. package/dist/tools/adb-logcat.js +6 -14
  17. package/dist/tools/adb-shell.d.ts +8 -28
  18. package/dist/tools/adb-shell.js +10 -16
  19. package/dist/tools/cache.d.ts +9 -32
  20. package/dist/tools/cache.js +9 -25
  21. package/dist/tools/emulator-device.d.ts +4 -25
  22. package/dist/tools/emulator-device.js +5 -27
  23. package/dist/tools/gradle-build.d.ts +4 -16
  24. package/dist/tools/gradle-build.js +5 -14
  25. package/dist/tools/gradle-get-details.d.ts +7 -27
  26. package/dist/tools/gradle-get-details.js +11 -27
  27. package/dist/tools/gradle-list.d.ts +4 -13
  28. package/dist/tools/gradle-list.js +5 -13
  29. package/dist/tools/gradle-test.d.ts +4 -20
  30. package/dist/tools/gradle-test.js +9 -19
  31. package/dist/tools/index.d.ts +197 -0
  32. package/dist/tools/index.js +33 -0
  33. package/dist/tools/rtfm.d.ts +4 -12
  34. package/dist/tools/rtfm.js +10 -11
  35. package/dist/tools/ui-action.d.ts +18 -41
  36. package/dist/tools/ui-action.js +179 -35
  37. package/dist/tools/ui-capture.d.ts +7 -26
  38. package/dist/tools/ui-capture.js +9 -21
  39. package/dist/tools/ui-query.d.ts +13 -72
  40. package/dist/tools/ui-query.js +39 -46
  41. package/dist/types/errors.d.ts +1 -0
  42. package/dist/types/errors.js +1 -0
  43. package/dist/types/schemas/ui-output.d.ts +92 -6
  44. package/dist/types/schemas/ui-output.js +20 -0
  45. package/docs/contracts/replicant-mcp.contract.json +241 -57
  46. package/docs/contracts/tool-schema-tokens.json +67 -0
  47. package/package.json +9 -6
@@ -50,11 +50,6 @@ export declare class UiAutomatorAdapter {
50
50
  private scalingState;
51
51
  constructor(adb?: AdbAdapter);
52
52
  getScalingState(): ScalingState | null;
53
- /**
54
- * Transforms accessibility tree nodes from device space to image space.
55
- * This ensures bounds/coordinates match the scaled screenshot when scaling is active.
56
- */
57
- private transformTreeToImageSpace;
58
53
  dump(deviceId: string): Promise<AccessibilityNode[]>;
59
54
  find(deviceId: string, selector: {
60
55
  resourceId?: string;
@@ -65,7 +60,12 @@ export declare class UiAutomatorAdapter {
65
60
  tap(deviceId: string, x: number, y: number, deviceSpace?: boolean): Promise<void>;
66
61
  tapElement(deviceId: string, element: AccessibilityNode): Promise<void>;
67
62
  input(deviceId: string, text: string): Promise<void>;
68
- scroll(deviceId: string, direction: "up" | "down" | "left" | "right", amount?: number): Promise<void>;
63
+ scroll(deviceId: string, direction: "up" | "down" | "left" | "right", amount?: number, bounds?: {
64
+ left: number;
65
+ top: number;
66
+ right: number;
67
+ bottom: number;
68
+ }): Promise<void>;
69
69
  screenshot(deviceId: string, options?: ScreenshotOptions): Promise<ScreenshotResult>;
70
70
  accessibilityCheck(deviceId: string): Promise<{
71
71
  hasAccessibleElements: boolean;
@@ -4,7 +4,7 @@ import sharp from "sharp";
4
4
  import { AdbAdapter } from "./adb.js";
5
5
  import { parseUiDump, findElements, flattenTree } from "../parsers/ui-dump.js";
6
6
  import { ReplicantError, ErrorCode } from "../types/index.js";
7
- import { calculateScaleFactor, toImageSpace, toDeviceSpace, boundsToImageSpace } from "../services/scaling.js";
7
+ import { calculateScaleFactor, toDeviceSpace } from "../services/scaling.js";
8
8
  import { getDefaultScreenshotPath } from "../utils/paths.js";
9
9
  import { findWithFallbacks } from "./ui-fallback-find.js";
10
10
  export class UiAutomatorAdapter {
@@ -17,27 +17,6 @@ export class UiAutomatorAdapter {
17
17
  getScalingState() {
18
18
  return this.scalingState;
19
19
  }
20
- /**
21
- * Transforms accessibility tree nodes from device space to image space.
22
- * This ensures bounds/coordinates match the scaled screenshot when scaling is active.
23
- */
24
- transformTreeToImageSpace(nodes) {
25
- if (!this.scalingState || this.scalingState.scaleFactor === 1.0) {
26
- return nodes;
27
- }
28
- const sf = this.scalingState.scaleFactor;
29
- return nodes.map((node) => {
30
- const newBounds = boundsToImageSpace(node.bounds, sf);
31
- const center = toImageSpace(node.centerX, node.centerY, sf);
32
- return {
33
- ...node,
34
- bounds: newBounds,
35
- centerX: center.x,
36
- centerY: center.y,
37
- children: node.children ? this.transformTreeToImageSpace(node.children) : [],
38
- };
39
- });
40
- }
41
20
  async dump(deviceId) {
42
21
  // Dump UI hierarchy to device
43
22
  await this.adb.shell(deviceId, "uiautomator dump /sdcard/ui-dump.xml");
@@ -45,8 +24,8 @@ export class UiAutomatorAdapter {
45
24
  const result = await this.adb.shell(deviceId, "cat /sdcard/ui-dump.xml");
46
25
  // Clean up
47
26
  await this.adb.shell(deviceId, "rm /sdcard/ui-dump.xml");
48
- const tree = parseUiDump(result.stdout);
49
- return this.transformTreeToImageSpace(tree);
27
+ // Coords stay in native uiautomator (device) space — see THE-96.
28
+ return parseUiDump(result.stdout);
50
29
  }
51
30
  async find(deviceId, selector) {
52
31
  const tree = await this.dump(deviceId);
@@ -72,14 +51,23 @@ export class UiAutomatorAdapter {
72
51
  const escaped = text.replace(/(['"\\$`])/g, "\\$1").replace(/ /g, "%s");
73
52
  await this.adb.shell(deviceId, `input text "${escaped}"`);
74
53
  }
75
- async scroll(deviceId, direction, amount = 0.5) {
76
- const screen = await this.getScreenMetadata(deviceId);
77
- const { width, height } = screen;
78
- // Calculate scroll distance based on amount (0-1 representing screen percentage)
54
+ async scroll(deviceId, direction, amount = 0.5, bounds) {
55
+ let width, height, centerX, centerY;
56
+ if (bounds) {
57
+ width = bounds.right - bounds.left;
58
+ height = bounds.bottom - bounds.top;
59
+ centerX = Math.round((bounds.left + bounds.right) / 2);
60
+ centerY = Math.round((bounds.top + bounds.bottom) / 2);
61
+ }
62
+ else {
63
+ const screen = await this.getScreenMetadata(deviceId);
64
+ width = screen.width;
65
+ height = screen.height;
66
+ centerX = Math.round(width / 2);
67
+ centerY = Math.round(height / 2);
68
+ }
69
+ // Calculate scroll distance based on amount (0-1 representing container percentage)
79
70
  const scrollDistance = Math.round((direction === "up" || direction === "down" ? height : width) * amount * 0.8);
80
- // Center point of the screen
81
- const centerX = Math.round(width / 2);
82
- const centerY = Math.round(height / 2);
83
71
  // Calculate start and end points based on direction
84
72
  // Note: "scroll down" means content moves up, so we swipe up (finger moves from bottom to top)
85
73
  let startX, startY, endX, endY;
@@ -3,6 +3,40 @@ import { extractText, searchText } from "../services/ocr.js";
3
3
  import { matchIconPattern, matchesResourceId } from "../services/icon-patterns.js";
4
4
  import { filterIconCandidates, formatBounds, cropCandidateImage } from "../services/visual-candidates.js";
5
5
  import { calculateGridCellBounds, calculatePositionCoordinates, createGridOverlay, POSITION_LABELS, } from "../services/grid.js";
6
+ import { boundsToImageSpace, toDeviceSpace } from "../services/scaling.js";
7
+ function ocrToDeviceSpace(elements, scaleFactor) {
8
+ if (scaleFactor === 1.0)
9
+ return elements;
10
+ return elements.map((el) => {
11
+ const center = toDeviceSpace(el.center.x, el.center.y, scaleFactor);
12
+ const m = el.bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
13
+ let bounds = el.bounds;
14
+ if (m) {
15
+ const x0 = Math.round(parseInt(m[1], 10) * scaleFactor);
16
+ const y0 = Math.round(parseInt(m[2], 10) * scaleFactor);
17
+ const x1 = Math.round(parseInt(m[3], 10) * scaleFactor);
18
+ const y1 = Math.round(parseInt(m[4], 10) * scaleFactor);
19
+ bounds = `[${x0},${y0}][${x1},${y1}]`;
20
+ }
21
+ return { ...el, center, bounds };
22
+ });
23
+ }
24
+ function gridResultToDeviceSpace(cellBounds, coords, scaleFactor) {
25
+ if (scaleFactor === 1.0) {
26
+ return {
27
+ bounds: `[${cellBounds.x0},${cellBounds.y0}][${cellBounds.x1},${cellBounds.y1}]`,
28
+ center: coords,
29
+ };
30
+ }
31
+ return {
32
+ bounds: `[${Math.round(cellBounds.x0 * scaleFactor)},${Math.round(cellBounds.y0 * scaleFactor)}]` +
33
+ `[${Math.round(cellBounds.x1 * scaleFactor)},${Math.round(cellBounds.y1 * scaleFactor)}]`,
34
+ center: {
35
+ x: Math.round(coords.x * scaleFactor),
36
+ y: Math.round(coords.y * scaleFactor),
37
+ },
38
+ };
39
+ }
6
40
  function createEarlyStopResult(tier, source) {
7
41
  return {
8
42
  elements: [],
@@ -20,10 +54,12 @@ export async function findWithFallbacks(deps, deviceId, selector, options = {})
20
54
  // Handle Tier 5 grid refinement FIRST (when gridCell and gridPosition are provided)
21
55
  if (options.gridCell !== undefined && options.gridPosition !== undefined) {
22
56
  let width, height;
57
+ let gridScaleFactor = 1.0;
23
58
  const scalingState = deps.getScalingState();
24
59
  if (scalingState && scalingState.scaleFactor !== 1.0) {
25
60
  width = scalingState.imageWidth;
26
61
  height = scalingState.imageHeight;
62
+ gridScaleFactor = scalingState.scaleFactor;
27
63
  }
28
64
  else {
29
65
  const screen = await deps.getScreenMetadata(deviceId);
@@ -32,12 +68,13 @@ export async function findWithFallbacks(deps, deviceId, selector, options = {})
32
68
  }
33
69
  const cellBounds = calculateGridCellBounds(options.gridCell, width, height);
34
70
  const coords = calculatePositionCoordinates(options.gridPosition, cellBounds);
71
+ const deviceCoords = gridResultToDeviceSpace(cellBounds, coords, gridScaleFactor);
35
72
  return {
36
73
  elements: [
37
74
  {
38
75
  index: 0,
39
- bounds: `[${cellBounds.x0},${cellBounds.y0}][${cellBounds.x1},${cellBounds.y1}]`,
40
- center: coords,
76
+ bounds: deviceCoords.bounds,
77
+ center: deviceCoords.center,
41
78
  },
42
79
  ],
43
80
  source: "grid",
@@ -86,12 +123,14 @@ export async function findWithFallbacks(deps, deviceId, selector, options = {})
86
123
  if ((selector.text || selector.textContains) && maxTier >= 3) {
87
124
  const searchTerm = selector.text || selector.textContains;
88
125
  const screenshotResult = await deps.screenshot(deviceId, {});
126
+ const scalingState = deps.getScalingState();
127
+ const scaleFactor = scalingState?.scaleFactor ?? 1.0;
89
128
  try {
90
129
  const ocrResults = await extractText(screenshotResult.path);
91
130
  const matches = searchText(ocrResults, searchTerm);
92
131
  if (matches.length > 0) {
93
132
  return {
94
- elements: matches,
133
+ elements: ocrToDeviceSpace(matches, scaleFactor),
95
134
  source: "ocr",
96
135
  tier: 3,
97
136
  confidence: "high",
@@ -108,11 +147,12 @@ export async function findWithFallbacks(deps, deviceId, selector, options = {})
108
147
  const flat = flattenTree(tree);
109
148
  const iconCandidates = filterIconCandidates(flat);
110
149
  if (iconCandidates.length > 0) {
150
+ // Crop the screenshot using image-space bounds (dump now returns device-space).
111
151
  const candidates = await Promise.all(iconCandidates.map(async (node, index) => ({
112
152
  index,
113
153
  bounds: formatBounds(node),
114
154
  center: { x: node.centerX, y: node.centerY },
115
- image: await cropCandidateImage(screenshotResult.path, node.bounds),
155
+ image: await cropCandidateImage(screenshotResult.path, boundsToImageSpace(node.bounds, scaleFactor)),
116
156
  })));
117
157
  const allUnlabeled = flat.filter((n) => n.clickable && !n.text && !n.contentDesc);
118
158
  return {
@@ -15,6 +15,7 @@ export interface AccessibilityNode {
15
15
  centerY: number;
16
16
  clickable: boolean;
17
17
  focusable: boolean;
18
+ scrollable?: boolean;
18
19
  children?: AccessibilityNode[];
19
20
  }
20
21
  export declare function parseUiDump(xml: string): AccessibilityNode[];
@@ -20,7 +20,7 @@ function parseBounds(boundsStr) {
20
20
  }
21
21
  function parseNodeFromAttrs(attrs) {
22
22
  const bounds = parseBounds(attrs.bounds || "[0,0][0,0]");
23
- return {
23
+ const node = {
24
24
  index: parseInt(attrs.index || "0", 10),
25
25
  text: attrs.text || "",
26
26
  resourceId: attrs["resource-id"] || "",
@@ -32,6 +32,35 @@ function parseNodeFromAttrs(attrs) {
32
32
  clickable: attrs.clickable === "true",
33
33
  focusable: attrs.focusable === "true",
34
34
  };
35
+ if (attrs.scrollable !== undefined) {
36
+ node.scrollable = attrs.scrollable === "true";
37
+ }
38
+ return node;
39
+ }
40
+ function collectDescendantLabels(node) {
41
+ const labels = [];
42
+ for (const child of node.children ?? []) {
43
+ const label = child.text || child.contentDesc;
44
+ if (label) {
45
+ // The child's label already represents its subtree (post-order: descendants
46
+ // were either labelled originally, or rolled up into this child's text).
47
+ // Recursing would double-count.
48
+ labels.push(label);
49
+ }
50
+ else {
51
+ labels.push(...collectDescendantLabels(child));
52
+ }
53
+ }
54
+ return labels;
55
+ }
56
+ function propagateChildLabels(node) {
57
+ for (const child of node.children ?? [])
58
+ propagateChildLabels(child);
59
+ if (!node.text && !node.contentDesc && node.children?.length) {
60
+ const labels = collectDescendantLabels(node);
61
+ if (labels.length > 0)
62
+ node.text = labels.join(" ");
63
+ }
35
64
  }
36
65
  export function parseUiDump(xml) {
37
66
  const nodes = [];
@@ -103,7 +132,10 @@ export function parseUiDump(xml) {
103
132
  // Extract hierarchy content
104
133
  const hierarchyMatch = xml.match(/<hierarchy[^>]*>([\s\S]*)<\/hierarchy>/);
105
134
  if (hierarchyMatch) {
106
- return parseChildren(hierarchyMatch[1]);
135
+ const tree = parseChildren(hierarchyMatch[1]);
136
+ for (const root of tree)
137
+ propagateChildLabels(root);
138
+ return tree;
107
139
  }
108
140
  return nodes;
109
141
  }
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ type McpInputSchema = {
3
+ type: "object";
4
+ properties?: Record<string, unknown>;
5
+ required?: string[];
6
+ };
7
+ export declare function toMcpJsonSchema(schema: z.ZodType): McpInputSchema;
8
+ export {};
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ // Recursively removes $schema and additionalProperties from a JSON schema
3
+ // tree. Strict-mode enforcement happens at the Zod validator; the wire
4
+ // payload stays compact and consistent (top-level and nested alike).
5
+ function stripMeta(node) {
6
+ if (Array.isArray(node))
7
+ return node.map(stripMeta);
8
+ if (node && typeof node === "object") {
9
+ const entries = Object.entries(node)
10
+ .filter(([k]) => k !== "$schema" && k !== "additionalProperties")
11
+ .map(([k, v]) => [k, stripMeta(v)]);
12
+ return Object.fromEntries(entries);
13
+ }
14
+ return node;
15
+ }
16
+ export function toMcpJsonSchema(schema) {
17
+ const json = z.toJSONSchema(schema);
18
+ return stripMeta(json);
19
+ }
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export type NumberOpts = {
3
+ min?: number;
4
+ max?: number;
5
+ };
6
+ export declare const numberInput: (opts?: NumberOpts) => z.ZodPipe<z.ZodTransform<string | number, unknown>, z.ZodCoercedNumber<unknown>>;
7
+ export declare const booleanInput: () => z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodBoolean>;
8
+ export declare const jsonObjectInput: <T extends z.ZodRawShape>(shape: T) => z.ZodPipe<z.ZodTransform<any, unknown>, z.ZodObject<{ -readonly [P in keyof T]: T[P]; }, z.core.$strict>>;
9
+ export declare const toolSchema: <T extends z.ZodRawShape>(shape: T) => z.ZodObject<{ -readonly [P in keyof T]: T[P]; }, z.core.$strict>;
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ // Accepts native numbers or numeric strings. Rejects null, booleans, arrays,
3
+ // objects, and other primitives that z.coerce.number() would silently coerce
4
+ // to 0/1/NaN. Use options for bounds since the preprocess wrapper loses
5
+ // .min()/.max() chainability.
6
+ export const numberInput = (opts = {}) => {
7
+ let base = z.coerce.number().finite();
8
+ if (opts.min !== undefined)
9
+ base = base.min(opts.min);
10
+ if (opts.max !== undefined)
11
+ base = base.max(opts.max);
12
+ return z.preprocess((v, ctx) => {
13
+ if (typeof v === "number" || typeof v === "string")
14
+ return v;
15
+ ctx.addIssue({
16
+ code: "custom",
17
+ message: `Expected number or numeric string, got ${v === null ? "null" : Array.isArray(v) ? "array" : typeof v}`,
18
+ });
19
+ return z.NEVER;
20
+ }, base);
21
+ };
22
+ // z.coerce.boolean() treats "false" as truthy — manual preprocess avoids the footgun.
23
+ export const booleanInput = () => z.preprocess((v) => {
24
+ if (typeof v === "boolean")
25
+ return v;
26
+ if (v === "true")
27
+ return true;
28
+ if (v === "false")
29
+ return false;
30
+ return v;
31
+ }, z.boolean());
32
+ // Accepts either a plain object or a JSON-string encoding. Inner object is
33
+ // strict so nested unknown fields are rejected (matches toolSchema).
34
+ export const jsonObjectInput = (shape) => z.preprocess((v) => {
35
+ if (typeof v !== "string")
36
+ return v;
37
+ try {
38
+ return JSON.parse(v);
39
+ }
40
+ catch {
41
+ return v;
42
+ }
43
+ }, z.object(shape).strict());
44
+ export const toolSchema = (shape) => z.object(shape).strict();
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, uiQueryInputSchema, uiQueryToolDefinition, handleUiQueryTool, uiActionInputSchema, uiActionToolDefinition, handleUiActionTool, uiCaptureInputSchema, uiCaptureToolDefinition, handleUiCaptureTool, } from "./tools/index.js";
9
+ import { ALL_TOOL_DEFINITIONS, cacheInputSchema, handleCacheTool, rtfmInputSchema, handleRtfmTool, adbDeviceInputSchema, handleAdbDeviceTool, adbAppInputSchema, handleAdbAppTool, adbLogcatInputSchema, handleAdbLogcatTool, adbShellInputSchema, handleAdbShellTool, emulatorDeviceInputSchema, handleEmulatorDeviceTool, gradleBuildInputSchema, handleGradleBuildTool, gradleTestInputSchema, handleGradleTestTool, gradleListInputSchema, handleGradleListTool, gradleGetDetailsInputSchema, handleGradleGetDetailsTool, uiQueryInputSchema, handleUiQueryTool, uiActionInputSchema, handleUiActionTool, uiCaptureInputSchema, handleUiCaptureTool, } from "./tools/index.js";
10
10
  export function createServerContext() {
11
11
  const environment = new EnvironmentService();
12
12
  const processRunner = new ProcessRunner(environment);
@@ -24,22 +24,7 @@ export function createServerContext() {
24
24
  lastFindResults: [],
25
25
  };
26
26
  }
27
- const toolDefinitions = [
28
- cacheToolDefinition,
29
- rtfmToolDefinition,
30
- adbDeviceToolDefinition,
31
- adbAppToolDefinition,
32
- adbLogcatToolDefinition,
33
- adbShellToolDefinition,
34
- emulatorDeviceToolDefinition,
35
- gradleBuildToolDefinition,
36
- gradleTestToolDefinition,
37
- gradleListToolDefinition,
38
- gradleGetDetailsToolDefinition,
39
- uiQueryToolDefinition,
40
- uiActionToolDefinition,
41
- uiCaptureToolDefinition,
42
- ];
27
+ const toolDefinitions = ALL_TOOL_DEFINITIONS;
43
28
  async function dispatchToolCall(name, args, context) {
44
29
  const rawArgs = args ?? {};
45
30
  const parseOrThrow = (toolName, parser) => {
@@ -85,7 +70,7 @@ async function dispatchToolCall(name, args, context) {
85
70
  case "ui-query":
86
71
  return handleUiQueryTool(parseOrThrow("ui-query", uiQueryInputSchema), context, context.config.getUiConfig());
87
72
  case "ui-action":
88
- return handleUiActionTool(parseOrThrow("ui-action", uiActionInputSchema), context);
73
+ return handleUiActionTool(parseOrThrow("ui-action", uiActionInputSchema), context, context.config.getUiConfig());
89
74
  case "ui-capture":
90
75
  return handleUiCaptureTool(parseOrThrow("ui-capture", uiCaptureInputSchema), context, context.config.getUiConfig());
91
76
  default:
@@ -11,43 +11,19 @@ export declare const adbAppInputSchema: z.ZodObject<{
11
11
  }>;
12
12
  apkPath: z.ZodOptional<z.ZodString>;
13
13
  packageName: z.ZodOptional<z.ZodString>;
14
- limit: z.ZodOptional<z.ZodNumber>;
14
+ limit: z.ZodOptional<z.ZodPipe<z.ZodTransform<string | number, unknown>, z.ZodCoercedNumber<unknown>>>;
15
15
  filter: z.ZodOptional<z.ZodString>;
16
- offset: z.ZodOptional<z.ZodNumber>;
17
- }, z.core.$strip>;
16
+ offset: z.ZodOptional<z.ZodPipe<z.ZodTransform<string | number, unknown>, z.ZodCoercedNumber<unknown>>>;
17
+ }, z.core.$strict>;
18
18
  export type AdbAppInput = z.infer<typeof adbAppInputSchema>;
19
19
  export declare function handleAdbAppTool(input: AdbAppInput, context: ServerContext): Promise<Record<string, unknown>>;
20
20
  export declare const adbAppToolDefinition: {
21
21
  name: string;
22
22
  description: string;
23
23
  inputSchema: {
24
- type: string;
25
- properties: {
26
- operation: {
27
- type: string;
28
- enum: string[];
29
- };
30
- apkPath: {
31
- type: string;
32
- description: string;
33
- };
34
- packageName: {
35
- type: string;
36
- };
37
- limit: {
38
- type: string;
39
- description: string;
40
- };
41
- filter: {
42
- type: string;
43
- description: string;
44
- };
45
- offset: {
46
- type: string;
47
- description: string;
48
- };
49
- };
50
- required: string[];
24
+ type: "object";
25
+ properties?: Record<string, unknown>;
26
+ required?: string[];
51
27
  };
52
28
  annotations: {
53
29
  readOnlyHint: boolean;
@@ -1,12 +1,16 @@
1
1
  import { z } from "zod";
2
2
  import { CACHE_TTLS, ReplicantError, ErrorCode } from "../types/index.js";
3
- export const adbAppInputSchema = z.object({
3
+ import { numberInput, toolSchema } from "../schemas/inputs.js";
4
+ import { toMcpJsonSchema } from "../schemas/derive.js";
5
+ export const adbAppInputSchema = toolSchema({
4
6
  operation: z.enum(["install", "uninstall", "launch", "stop", "clear-data", "list"]),
5
- apkPath: z.string().optional(),
7
+ apkPath: z.string().optional().describe("APK path"),
6
8
  packageName: z.string().optional(),
7
- limit: z.number().min(1).max(100).optional(),
8
- filter: z.string().optional(),
9
- offset: z.number().min(0).optional(),
9
+ limit: numberInput({ min: 1, max: 100 })
10
+ .optional()
11
+ .describe("Default: 20, max: 100"),
12
+ filter: z.string().optional().describe("Filter by name (case-insensitive)"),
13
+ offset: numberInput({ min: 0 }).optional().describe("Pagination offset"),
10
14
  });
11
15
  async function handleInstall(input, deviceId, context) {
12
16
  if (!input.apkPath) {
@@ -85,30 +89,7 @@ export async function handleAdbAppTool(input, context) {
85
89
  export const adbAppToolDefinition = {
86
90
  name: "adb-app",
87
91
  description: "Manage applications.",
88
- inputSchema: {
89
- type: "object",
90
- properties: {
91
- operation: {
92
- type: "string",
93
- enum: ["install", "uninstall", "launch", "stop", "clear-data", "list"],
94
- },
95
- apkPath: { type: "string", description: "APK path" },
96
- packageName: { type: "string" },
97
- limit: {
98
- type: "number",
99
- description: "Default: 20, max: 100",
100
- },
101
- filter: {
102
- type: "string",
103
- description: "Filter by name (case-insensitive)",
104
- },
105
- offset: {
106
- type: "number",
107
- description: "Pagination offset",
108
- },
109
- },
110
- required: ["operation"],
111
- },
92
+ inputSchema: toMcpJsonSchema(adbAppInputSchema),
112
93
  annotations: {
113
94
  readOnlyHint: false,
114
95
  destructiveHint: true,
@@ -9,24 +9,16 @@ export declare const adbDeviceInputSchema: z.ZodObject<{
9
9
  "health-check": "health-check";
10
10
  }>;
11
11
  deviceId: z.ZodOptional<z.ZodString>;
12
- }, z.core.$strip>;
12
+ }, z.core.$strict>;
13
13
  export type AdbDeviceInput = z.infer<typeof adbDeviceInputSchema>;
14
14
  export declare function handleAdbDeviceTool(input: AdbDeviceInput, context: ServerContext): Promise<Record<string, unknown>>;
15
15
  export declare const adbDeviceToolDefinition: {
16
16
  name: string;
17
17
  description: string;
18
18
  inputSchema: {
19
- type: string;
20
- properties: {
21
- operation: {
22
- type: string;
23
- enum: string[];
24
- };
25
- deviceId: {
26
- type: string;
27
- };
28
- };
29
- required: string[];
19
+ type: "object";
20
+ properties?: Record<string, unknown>;
21
+ required?: string[];
30
22
  };
31
23
  annotations: {
32
24
  readOnlyHint: boolean;
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { CACHE_TTLS, ReplicantError, ErrorCode } from "../types/index.js";
3
- export const adbDeviceInputSchema = z.object({
3
+ import { toolSchema } from "../schemas/inputs.js";
4
+ import { toMcpJsonSchema } from "../schemas/derive.js";
5
+ export const adbDeviceInputSchema = toolSchema({
4
6
  operation: z.enum(["list", "select", "wait", "properties", "health-check"]),
5
7
  deviceId: z.string().optional(),
6
8
  });
@@ -111,17 +113,7 @@ export async function handleAdbDeviceTool(input, context) {
111
113
  export const adbDeviceToolDefinition = {
112
114
  name: "adb-device",
113
115
  description: "Manage device connections.",
114
- inputSchema: {
115
- type: "object",
116
- properties: {
117
- operation: {
118
- type: "string",
119
- enum: ["list", "select", "wait", "properties", "health-check"],
120
- },
121
- deviceId: { type: "string" },
122
- },
123
- required: ["operation"],
124
- },
116
+ inputSchema: toMcpJsonSchema(adbDeviceInputSchema),
125
117
  annotations: {
126
118
  readOnlyHint: false,
127
119
  destructiveHint: false,
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { ServerContext } from "../server.js";
3
3
  export declare const adbLogcatInputSchema: z.ZodObject<{
4
- lines: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
4
+ lines: z.ZodDefault<z.ZodOptional<z.ZodPipe<z.ZodTransform<string | number, unknown>, z.ZodCoercedNumber<unknown>>>>;
5
5
  package: z.ZodOptional<z.ZodString>;
6
6
  tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
7
  level: z.ZodOptional<z.ZodEnum<{
@@ -13,40 +13,16 @@ export declare const adbLogcatInputSchema: z.ZodObject<{
13
13
  }>>;
14
14
  rawFilter: z.ZodOptional<z.ZodString>;
15
15
  since: z.ZodOptional<z.ZodString>;
16
- }, z.core.$strip>;
16
+ }, z.core.$strict>;
17
17
  export type AdbLogcatInput = z.infer<typeof adbLogcatInputSchema>;
18
18
  export declare function handleAdbLogcatTool(input: AdbLogcatInput, context: ServerContext): Promise<Record<string, unknown>>;
19
19
  export declare const adbLogcatToolDefinition: {
20
20
  name: string;
21
21
  description: string;
22
22
  inputSchema: {
23
- type: string;
24
- properties: {
25
- lines: {
26
- type: string;
27
- description: string;
28
- };
29
- package: {
30
- type: string;
31
- };
32
- tags: {
33
- type: string;
34
- items: {
35
- type: string;
36
- };
37
- };
38
- level: {
39
- type: string;
40
- enum: string[];
41
- };
42
- rawFilter: {
43
- type: string;
44
- };
45
- since: {
46
- type: string;
47
- description: string;
48
- };
49
- };
23
+ type: "object";
24
+ properties?: Record<string, unknown>;
25
+ required?: string[];
50
26
  };
51
27
  annotations: {
52
28
  readOnlyHint: boolean;
@@ -1,12 +1,14 @@
1
1
  import { z } from "zod";
2
2
  import { CACHE_TTLS } from "../types/index.js";
3
- export const adbLogcatInputSchema = z.object({
4
- lines: z.number().optional().default(100),
3
+ import { numberInput, toolSchema } from "../schemas/inputs.js";
4
+ import { toMcpJsonSchema } from "../schemas/derive.js";
5
+ export const adbLogcatInputSchema = toolSchema({
6
+ lines: numberInput().optional().default(100).describe("Default: 100"),
5
7
  package: z.string().optional(),
6
8
  tags: z.array(z.string()).optional(),
7
9
  level: z.enum(["verbose", "debug", "info", "warn", "error"]).optional(),
8
10
  rawFilter: z.string().optional(),
9
- since: z.string().optional(),
11
+ since: z.string().optional().describe("e.g., '01-20 15:30:00.000'"),
10
12
  });
11
13
  function buildLogcatFilter(input) {
12
14
  if (input.rawFilter)
@@ -55,17 +57,7 @@ export async function handleAdbLogcatTool(input, context) {
55
57
  export const adbLogcatToolDefinition = {
56
58
  name: "adb-logcat",
57
59
  description: "Read device logs. Returns summary with logId.",
58
- inputSchema: {
59
- type: "object",
60
- properties: {
61
- lines: { type: "number", description: "Default: 100" },
62
- package: { type: "string" },
63
- tags: { type: "array", items: { type: "string" } },
64
- level: { type: "string", enum: ["verbose", "debug", "info", "warn", "error"] },
65
- rawFilter: { type: "string" },
66
- since: { type: "string", description: "e.g., '01-20 15:30:00.000'" },
67
- },
68
- },
60
+ inputSchema: toMcpJsonSchema(adbLogcatInputSchema),
69
61
  annotations: {
70
62
  readOnlyHint: true,
71
63
  destructiveHint: false,