replicant-mcp 1.6.3 → 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.
- package/dist/adapters/ui-automator.d.ts +6 -6
- package/dist/adapters/ui-automator.js +19 -31
- package/dist/adapters/ui-fallback-find.js +44 -4
- package/dist/parsers/ui-dump.d.ts +1 -0
- package/dist/parsers/ui-dump.js +34 -2
- package/dist/schemas/derive.d.ts +8 -0
- package/dist/schemas/derive.js +19 -0
- package/dist/schemas/inputs.d.ts +9 -0
- package/dist/schemas/inputs.js +44 -0
- package/dist/server.js +3 -18
- package/dist/tools/adb-app.d.ts +6 -30
- package/dist/tools/adb-app.js +10 -29
- package/dist/tools/adb-device.d.ts +4 -12
- package/dist/tools/adb-device.js +4 -12
- package/dist/tools/adb-logcat.d.ts +5 -29
- package/dist/tools/adb-logcat.js +6 -14
- package/dist/tools/adb-shell.d.ts +8 -28
- package/dist/tools/adb-shell.js +10 -16
- package/dist/tools/cache.d.ts +9 -32
- package/dist/tools/cache.js +9 -25
- package/dist/tools/emulator-device.d.ts +4 -25
- package/dist/tools/emulator-device.js +5 -27
- package/dist/tools/gradle-build.d.ts +4 -16
- package/dist/tools/gradle-build.js +5 -14
- package/dist/tools/gradle-get-details.d.ts +7 -27
- package/dist/tools/gradle-get-details.js +11 -27
- package/dist/tools/gradle-list.d.ts +4 -13
- package/dist/tools/gradle-list.js +5 -13
- package/dist/tools/gradle-test.d.ts +4 -20
- package/dist/tools/gradle-test.js +9 -19
- package/dist/tools/index.d.ts +197 -0
- package/dist/tools/index.js +33 -0
- package/dist/tools/rtfm.d.ts +4 -12
- package/dist/tools/rtfm.js +10 -11
- package/dist/tools/ui-action.d.ts +18 -41
- package/dist/tools/ui-action.js +179 -35
- package/dist/tools/ui-capture.d.ts +7 -26
- package/dist/tools/ui-capture.js +9 -21
- package/dist/tools/ui-query.d.ts +13 -72
- package/dist/tools/ui-query.js +39 -46
- package/dist/types/errors.d.ts +1 -0
- package/dist/types/errors.js +1 -0
- package/dist/types/schemas/ui-output.d.ts +92 -6
- package/dist/types/schemas/ui-output.js +20 -0
- package/docs/contracts/replicant-mcp.contract.json +241 -57
- package/docs/contracts/tool-schema-tokens.json +67 -0
- 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
|
|
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,
|
|
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
|
-
|
|
49
|
-
return
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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:
|
|
40
|
-
center:
|
|
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 {
|
package/dist/parsers/ui-dump.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,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 {
|
|
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:
|
package/dist/tools/adb-app.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
17
|
-
}, z.core.$
|
|
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:
|
|
25
|
-
properties
|
|
26
|
-
|
|
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;
|
package/dist/tools/adb-app.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CACHE_TTLS, ReplicantError, ErrorCode } from "../types/index.js";
|
|
3
|
-
|
|
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:
|
|
8
|
-
|
|
9
|
-
|
|
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.$
|
|
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:
|
|
20
|
-
properties
|
|
21
|
-
|
|
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;
|
package/dist/tools/adb-device.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CACHE_TTLS, ReplicantError, ErrorCode } from "../types/index.js";
|
|
3
|
-
|
|
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.
|
|
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.$
|
|
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:
|
|
24
|
-
properties
|
|
25
|
-
|
|
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;
|
package/dist/tools/adb-logcat.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { CACHE_TTLS } from "../types/index.js";
|
|
3
|
-
|
|
4
|
-
|
|
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,
|