replicant-mcp 1.4.1 → 1.4.3
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/README.md +72 -24
- package/dist/services/grid.d.ts +1 -1
- package/dist/services/grid.js +14 -3
- package/dist/tools/adb-app.d.ts +15 -0
- package/dist/tools/adb-app.js +41 -2
- package/dist/tools/adb-device.js +12 -2
- package/dist/tools/ui.d.ts +13 -0
- package/dist/tools/ui.js +47 -3
- package/dist/types/cache.d.ts +1 -0
- package/dist/types/cache.js +1 -0
- package/docs/rtfm/adb.md +48 -0
- package/docs/rtfm/build.md +52 -0
- package/docs/rtfm/emulator.md +23 -0
- package/docs/rtfm/index.md +9 -0
- package/docs/rtfm/ui.md +107 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -64,28 +64,30 @@ emulator -version # Should show Android emulator version
|
|
|
64
64
|
|
|
65
65
|
### Installation
|
|
66
66
|
|
|
67
|
-
**npm (recommended):**
|
|
68
67
|
```bash
|
|
69
68
|
npm install -g replicant-mcp
|
|
70
69
|
```
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
### Updating
|
|
72
|
+
|
|
73
73
|
```bash
|
|
74
|
-
|
|
75
|
-
cd replicant-mcp
|
|
76
|
-
npm install && npm run build
|
|
74
|
+
npm update -g replicant-mcp
|
|
77
75
|
```
|
|
78
76
|
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Setup
|
|
80
|
+
|
|
79
81
|
### Claude Desktop
|
|
80
82
|
|
|
81
|
-
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
83
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
82
84
|
|
|
83
85
|
```json
|
|
84
86
|
{
|
|
85
87
|
"mcpServers": {
|
|
86
88
|
"replicant": {
|
|
87
|
-
"command": "
|
|
88
|
-
"args": ["
|
|
89
|
+
"command": "npx",
|
|
90
|
+
"args": ["-y", "replicant-mcp"]
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
}
|
|
@@ -94,32 +96,78 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
|
|
94
96
|
### Claude Code
|
|
95
97
|
|
|
96
98
|
```bash
|
|
97
|
-
claude mcp add replicant
|
|
98
|
-
-e ANDROID_HOME=$HOME/Library/Android/sdk \
|
|
99
|
-
-e PATH="$HOME/Library/Android/sdk/platform-tools:$HOME/Library/Android/sdk/emulator:$HOME/Library/Android/sdk/cmdline-tools/latest/bin:$PATH" \
|
|
100
|
-
-- node $(npm root -g)/replicant-mcp/dist/index.js
|
|
99
|
+
claude mcp add replicant -- npx -y replicant-mcp
|
|
101
100
|
```
|
|
102
101
|
|
|
103
|
-
|
|
102
|
+
### Cursor
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
|
|
106
105
|
|
|
107
|
-
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"replicant": {
|
|
110
|
+
"command": "npx",
|
|
111
|
+
"args": ["-y", "replicant-mcp"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Windsurf
|
|
118
|
+
|
|
119
|
+
Add to `~/.codeium/windsurf/mcp_config.json`:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"replicant": {
|
|
125
|
+
"command": "npx",
|
|
126
|
+
"args": ["-y", "replicant-mcp"]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### OpenAI Codex CLI
|
|
108
133
|
|
|
134
|
+
Add to `~/.codex/config.toml`:
|
|
135
|
+
|
|
136
|
+
```toml
|
|
137
|
+
[mcp_servers.replicant]
|
|
138
|
+
command = "npx"
|
|
139
|
+
args = ["-y", "replicant-mcp"]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Or via CLI:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
codex mcp add replicant -- npx -y replicant-mcp
|
|
109
146
|
```
|
|
110
|
-
You: "The app crashed after I tapped Login. What happened?"
|
|
111
147
|
|
|
112
|
-
|
|
113
|
-
[Calls adb-logcat with level: "error", lines: 50]
|
|
148
|
+
> **Note:** Ensure `adb` and `emulator` are in your PATH, or set `ANDROID_HOME` environment variable. On macOS: `~/Library/Android/sdk`, on Linux: `~/Android/Sdk`.
|
|
114
149
|
|
|
115
|
-
|
|
150
|
+
---
|
|
116
151
|
|
|
117
|
-
|
|
118
|
-
'void com.example.User.getName()' on a null object reference
|
|
119
|
-
at com.example.ProfileActivity.onCreate(ProfileActivity.kt:47)
|
|
152
|
+
## Example
|
|
120
153
|
|
|
121
|
-
|
|
122
|
-
|
|
154
|
+
```
|
|
155
|
+
You: "I just changed the checkout flow. Build the app, go through checkout
|
|
156
|
+
with a test product, and make sure the confirmation screen shows up."
|
|
157
|
+
|
|
158
|
+
Claude: [Builds debug APK, installs on emulator, launches app]
|
|
159
|
+
[Taps "Electronics" category → "Wireless Headphones" → "Add to Cart"]
|
|
160
|
+
[Navigates to cart, taps "Checkout"]
|
|
161
|
+
[Fills shipping form, taps "Place Order"]
|
|
162
|
+
|
|
163
|
+
Confirmation screen is showing:
|
|
164
|
+
- Order #38291
|
|
165
|
+
- "Wireless Headphones" × 1
|
|
166
|
+
- Total: $79.99
|
|
167
|
+
- Estimated delivery: Jan 30
|
|
168
|
+
|
|
169
|
+
The flow works. Want me to test the edge cases—empty cart,
|
|
170
|
+
invalid address, payment failure?
|
|
123
171
|
```
|
|
124
172
|
|
|
125
173
|
---
|
package/dist/services/grid.d.ts
CHANGED
|
@@ -23,6 +23,6 @@ export declare function calculatePositionCoordinates(position: 1 | 2 | 3 | 4 | 5
|
|
|
23
23
|
export declare const POSITION_LABELS: string[];
|
|
24
24
|
/**
|
|
25
25
|
* Create a 4x6 numbered grid overlay on a screenshot.
|
|
26
|
-
* Returns base64
|
|
26
|
+
* Returns base64 JPEG (compressed for context efficiency).
|
|
27
27
|
*/
|
|
28
28
|
export declare function createGridOverlay(imagePath: string): Promise<string>;
|
package/dist/services/grid.js
CHANGED
|
@@ -54,15 +54,21 @@ export const POSITION_LABELS = [
|
|
|
54
54
|
];
|
|
55
55
|
const GRID_LINE_WIDTH = 3;
|
|
56
56
|
const LABEL_FONT_SIZE = 36;
|
|
57
|
+
const MAX_GRID_DIMENSION = 1000;
|
|
58
|
+
const JPEG_QUALITY = 70;
|
|
57
59
|
/**
|
|
58
60
|
* Create a 4x6 numbered grid overlay on a screenshot.
|
|
59
|
-
* Returns base64
|
|
61
|
+
* Returns base64 JPEG (compressed for context efficiency).
|
|
60
62
|
*/
|
|
61
63
|
export async function createGridOverlay(imagePath) {
|
|
62
64
|
const image = sharp(imagePath);
|
|
63
65
|
const metadata = await image.metadata();
|
|
64
66
|
const width = metadata.width;
|
|
65
67
|
const height = metadata.height;
|
|
68
|
+
// Calculate scaling to fit within MAX_GRID_DIMENSION
|
|
69
|
+
const scaleFactor = Math.min(1, MAX_GRID_DIMENSION / Math.max(width, height));
|
|
70
|
+
const scaledWidth = Math.round(width * scaleFactor);
|
|
71
|
+
const scaledHeight = Math.round(height * scaleFactor);
|
|
66
72
|
const cellWidth = width / GRID_COLS;
|
|
67
73
|
const cellHeight = height / GRID_ROWS;
|
|
68
74
|
// Create SVG overlay with grid lines and numbers
|
|
@@ -90,9 +96,14 @@ export async function createGridOverlay(imagePath) {
|
|
|
90
96
|
svgParts.push(`<text x="${centerX}" y="${centerY}" font-family="Arial, sans-serif" font-size="${LABEL_FONT_SIZE}" font-weight="bold" fill="white" text-anchor="middle" dominant-baseline="central">${cell}</text>`);
|
|
91
97
|
}
|
|
92
98
|
const svgOverlay = `<svg width="${width}" height="${height}">${svgParts.join("")}</svg>`;
|
|
93
|
-
|
|
99
|
+
// Composite at original size first, then resize
|
|
100
|
+
const composited = await image
|
|
94
101
|
.composite([{ input: Buffer.from(svgOverlay), top: 0, left: 0 }])
|
|
95
|
-
.
|
|
102
|
+
.toBuffer();
|
|
103
|
+
// Resize and compress to JPEG
|
|
104
|
+
const buffer = await sharp(composited)
|
|
105
|
+
.resize(scaledWidth, scaledHeight)
|
|
106
|
+
.jpeg({ quality: JPEG_QUALITY })
|
|
96
107
|
.toBuffer();
|
|
97
108
|
return buffer.toString("base64");
|
|
98
109
|
}
|
package/dist/tools/adb-app.d.ts
CHANGED
|
@@ -11,6 +11,9 @@ 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>;
|
|
15
|
+
filter: z.ZodOptional<z.ZodString>;
|
|
16
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
14
17
|
}, z.core.$strip>;
|
|
15
18
|
export type AdbAppInput = z.infer<typeof adbAppInputSchema>;
|
|
16
19
|
export declare function handleAdbAppTool(input: AdbAppInput, context: ServerContext): Promise<Record<string, unknown>>;
|
|
@@ -32,6 +35,18 @@ export declare const adbAppToolDefinition: {
|
|
|
32
35
|
type: string;
|
|
33
36
|
description: string;
|
|
34
37
|
};
|
|
38
|
+
limit: {
|
|
39
|
+
type: string;
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
filter: {
|
|
43
|
+
type: string;
|
|
44
|
+
description: string;
|
|
45
|
+
};
|
|
46
|
+
offset: {
|
|
47
|
+
type: string;
|
|
48
|
+
description: string;
|
|
49
|
+
};
|
|
35
50
|
};
|
|
36
51
|
required: string[];
|
|
37
52
|
};
|
package/dist/tools/adb-app.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { CACHE_TTLS } from "../types/index.js";
|
|
2
3
|
export const adbAppInputSchema = z.object({
|
|
3
4
|
operation: z.enum(["install", "uninstall", "launch", "stop", "clear-data", "list"]),
|
|
4
5
|
apkPath: z.string().optional(),
|
|
5
6
|
packageName: z.string().optional(),
|
|
7
|
+
// List operation options
|
|
8
|
+
limit: z.number().min(1).max(100).optional(),
|
|
9
|
+
filter: z.string().optional(),
|
|
10
|
+
offset: z.number().min(0).optional(),
|
|
6
11
|
});
|
|
7
12
|
export async function handleAdbAppTool(input, context) {
|
|
8
13
|
const device = await context.deviceState.ensureDevice(context.adb);
|
|
@@ -44,8 +49,30 @@ export async function handleAdbAppTool(input, context) {
|
|
|
44
49
|
return { cleared: input.packageName, deviceId };
|
|
45
50
|
}
|
|
46
51
|
case "list": {
|
|
47
|
-
const
|
|
48
|
-
|
|
52
|
+
const allPackages = await context.adb.getPackages(deviceId);
|
|
53
|
+
const limit = input.limit ?? 20;
|
|
54
|
+
const offset = input.offset ?? 0;
|
|
55
|
+
const filter = input.filter?.toLowerCase();
|
|
56
|
+
// Apply filter if provided
|
|
57
|
+
const filtered = filter
|
|
58
|
+
? allPackages.filter((pkg) => pkg.toLowerCase().includes(filter))
|
|
59
|
+
: allPackages;
|
|
60
|
+
// Paginate
|
|
61
|
+
const paginated = filtered.slice(offset, offset + limit);
|
|
62
|
+
const hasMore = offset + limit < filtered.length;
|
|
63
|
+
// Cache full list for subsequent requests
|
|
64
|
+
const cacheId = context.cache.generateId("app-list");
|
|
65
|
+
context.cache.set(cacheId, { packages: filtered, deviceId, filter: filter || null }, "app-list", CACHE_TTLS.APP_LIST);
|
|
66
|
+
return {
|
|
67
|
+
packages: paginated,
|
|
68
|
+
count: paginated.length,
|
|
69
|
+
totalCount: filtered.length,
|
|
70
|
+
hasMore,
|
|
71
|
+
offset,
|
|
72
|
+
limit,
|
|
73
|
+
cacheId,
|
|
74
|
+
deviceId,
|
|
75
|
+
};
|
|
49
76
|
}
|
|
50
77
|
default:
|
|
51
78
|
throw new Error(`Unknown operation: ${input.operation}`);
|
|
@@ -63,6 +90,18 @@ export const adbAppToolDefinition = {
|
|
|
63
90
|
},
|
|
64
91
|
apkPath: { type: "string", description: "Path to APK file (for install)" },
|
|
65
92
|
packageName: { type: "string", description: "Package name (for other operations)" },
|
|
93
|
+
limit: {
|
|
94
|
+
type: "number",
|
|
95
|
+
description: "Max packages to return (default: 20, max: 100). For list operation.",
|
|
96
|
+
},
|
|
97
|
+
filter: {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "Filter packages by name (case-insensitive contains). For list operation.",
|
|
100
|
+
},
|
|
101
|
+
offset: {
|
|
102
|
+
type: "number",
|
|
103
|
+
description: "Skip first N packages for pagination. For list operation.",
|
|
104
|
+
},
|
|
66
105
|
},
|
|
67
106
|
required: ["operation"],
|
|
68
107
|
},
|
package/dist/tools/adb-device.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { CACHE_TTLS } from "../types/index.js";
|
|
2
3
|
export const adbDeviceInputSchema = z.object({
|
|
3
4
|
operation: z.enum(["list", "select", "wait", "properties", "health-check"]),
|
|
4
5
|
deviceId: z.string().optional(),
|
|
@@ -41,16 +42,25 @@ export async function handleAdbDeviceTool(input, context) {
|
|
|
41
42
|
: await context.deviceState.ensureDevice(context.adb);
|
|
42
43
|
const deviceId = device.id;
|
|
43
44
|
const props = await context.adb.getProperties(deviceId);
|
|
45
|
+
// Cache full properties for retrieval via cache tool
|
|
46
|
+
const cacheId = context.cache.generateId("device-props");
|
|
47
|
+
context.cache.set(cacheId, { deviceId, properties: props }, "device-props", CACHE_TTLS.DEVICE_PROPERTIES);
|
|
48
|
+
// Return summary only (key properties + cache ID for full details)
|
|
44
49
|
return {
|
|
45
50
|
deviceId,
|
|
46
|
-
|
|
51
|
+
summary: {
|
|
47
52
|
model: props["ro.product.model"],
|
|
48
53
|
manufacturer: props["ro.product.manufacturer"],
|
|
49
54
|
sdkVersion: props["ro.build.version.sdk"],
|
|
50
55
|
androidVersion: props["ro.build.version.release"],
|
|
51
56
|
buildId: props["ro.build.id"],
|
|
57
|
+
device: props["ro.product.device"],
|
|
58
|
+
product: props["ro.product.name"],
|
|
59
|
+
hardware: props["ro.hardware"],
|
|
60
|
+
abiList: props["ro.product.cpu.abilist"],
|
|
52
61
|
},
|
|
53
|
-
|
|
62
|
+
propertyCount: Object.keys(props).length,
|
|
63
|
+
cacheId,
|
|
54
64
|
};
|
|
55
65
|
}
|
|
56
66
|
case "health-check": {
|
package/dist/tools/ui.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare const uiInputSchema: z.ZodObject<{
|
|
|
39
39
|
right: "right";
|
|
40
40
|
}>>;
|
|
41
41
|
amount: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
42
44
|
}, z.core.$strip>;
|
|
43
45
|
export type UiInput = z.infer<typeof uiInputSchema>;
|
|
44
46
|
export declare function handleUiTool(input: UiInput, context: ServerContext, uiConfig?: UiConfig): Promise<Record<string, unknown>>;
|
|
@@ -141,6 +143,17 @@ export declare const uiToolDefinition: {
|
|
|
141
143
|
maximum: number;
|
|
142
144
|
description: string;
|
|
143
145
|
};
|
|
146
|
+
limit: {
|
|
147
|
+
type: string;
|
|
148
|
+
minimum: number;
|
|
149
|
+
maximum: number;
|
|
150
|
+
description: string;
|
|
151
|
+
};
|
|
152
|
+
offset: {
|
|
153
|
+
type: string;
|
|
154
|
+
minimum: number;
|
|
155
|
+
description: string;
|
|
156
|
+
};
|
|
144
157
|
};
|
|
145
158
|
required: string[];
|
|
146
159
|
};
|
package/dist/tools/ui.js
CHANGED
|
@@ -25,6 +25,9 @@ export const uiInputSchema = z.object({
|
|
|
25
25
|
compact: z.boolean().optional(),
|
|
26
26
|
direction: z.enum(["up", "down", "left", "right"]).optional(),
|
|
27
27
|
amount: z.number().min(0).max(1).optional(),
|
|
28
|
+
// Pagination for dump operation
|
|
29
|
+
limit: z.number().min(1).max(100).optional(),
|
|
30
|
+
offset: z.number().min(0).optional(),
|
|
28
31
|
});
|
|
29
32
|
// Store last find results for elementIndex reference
|
|
30
33
|
// Updated to support accessibility, OCR, and grid elements
|
|
@@ -107,18 +110,47 @@ export async function handleUiTool(input, context, uiConfig) {
|
|
|
107
110
|
// Cache the tree
|
|
108
111
|
const dumpId = context.cache.generateId("ui-dump");
|
|
109
112
|
context.cache.set(dumpId, { tree, deviceId }, "ui-dump", CACHE_TTLS.UI_TREE);
|
|
113
|
+
// Warning for empty dumps
|
|
114
|
+
const emptyWarning = tree.length === 0
|
|
115
|
+
? "No accessibility nodes found. Possible causes: (1) UI still loading - wait and retry, (2) App uses custom rendering (Flutter, games, video players) - use screenshot instead, (3) App blocks accessibility services."
|
|
116
|
+
: undefined;
|
|
110
117
|
if (input.compact) {
|
|
111
118
|
// Compact mode: flat list of interactive elements only
|
|
112
119
|
const flat = flattenTree(tree);
|
|
113
120
|
const interactive = flat.filter((n) => n.clickable || n.focusable);
|
|
114
|
-
|
|
121
|
+
// Pagination
|
|
122
|
+
const limit = input.limit ?? 20;
|
|
123
|
+
const offset = input.offset ?? 0;
|
|
124
|
+
const totalCount = interactive.length;
|
|
125
|
+
const paginated = interactive.slice(offset, offset + limit);
|
|
126
|
+
const hasMore = offset + limit < totalCount;
|
|
127
|
+
const elements = paginated.map((n) => ({
|
|
115
128
|
text: n.text || n.contentDesc || undefined,
|
|
116
129
|
type: n.className.split(".").pop(),
|
|
117
130
|
x: n.centerX,
|
|
118
131
|
y: n.centerY,
|
|
119
132
|
resourceId: n.resourceId ? n.resourceId.split("/").pop() : undefined,
|
|
120
133
|
}));
|
|
121
|
-
|
|
134
|
+
// Also warn if tree has nodes but no interactive elements
|
|
135
|
+
const noInteractiveWarning = tree.length > 0 && totalCount === 0
|
|
136
|
+
? "Accessibility tree exists but no interactive elements found. Try 'ui find' with a text selector, or use screenshot for visual targeting."
|
|
137
|
+
: undefined;
|
|
138
|
+
// Hint for pagination
|
|
139
|
+
const hint = hasMore
|
|
140
|
+
? `${elements.length} of ${totalCount} elements shown. Use 'ui find' for specific elements, or add offset: ${offset + limit} for more.`
|
|
141
|
+
: undefined;
|
|
142
|
+
return {
|
|
143
|
+
dumpId,
|
|
144
|
+
elements,
|
|
145
|
+
count: elements.length,
|
|
146
|
+
totalCount,
|
|
147
|
+
hasMore,
|
|
148
|
+
offset,
|
|
149
|
+
limit,
|
|
150
|
+
deviceId,
|
|
151
|
+
hint,
|
|
152
|
+
warning: emptyWarning || noInteractiveWarning,
|
|
153
|
+
};
|
|
122
154
|
}
|
|
123
155
|
// Full mode: hierarchical tree with all details
|
|
124
156
|
const simplifyNode = (node, depth = 0) => ({
|
|
@@ -133,6 +165,7 @@ export async function handleUiTool(input, context, uiConfig) {
|
|
|
133
165
|
dumpId,
|
|
134
166
|
tree: tree.map((n) => simplifyNode(n)),
|
|
135
167
|
deviceId,
|
|
168
|
+
warning: emptyWarning,
|
|
136
169
|
};
|
|
137
170
|
}
|
|
138
171
|
case "find": {
|
|
@@ -410,7 +443,7 @@ export const uiToolDefinition = {
|
|
|
410
443
|
},
|
|
411
444
|
compact: {
|
|
412
445
|
type: "boolean",
|
|
413
|
-
description: "For dump: return flat list of interactive elements
|
|
446
|
+
description: "For dump: return paginated flat list of interactive elements (default: 20, use limit/offset for more).",
|
|
414
447
|
},
|
|
415
448
|
direction: {
|
|
416
449
|
type: "string",
|
|
@@ -423,6 +456,17 @@ export const uiToolDefinition = {
|
|
|
423
456
|
maximum: 1,
|
|
424
457
|
description: "Scroll amount as fraction of screen (0-1, default: 0.5)",
|
|
425
458
|
},
|
|
459
|
+
limit: {
|
|
460
|
+
type: "number",
|
|
461
|
+
minimum: 1,
|
|
462
|
+
maximum: 100,
|
|
463
|
+
description: "For dump with compact: max elements to return (default: 20).",
|
|
464
|
+
},
|
|
465
|
+
offset: {
|
|
466
|
+
type: "number",
|
|
467
|
+
minimum: 0,
|
|
468
|
+
description: "For dump with compact: skip first N elements for pagination.",
|
|
469
|
+
},
|
|
426
470
|
},
|
|
427
471
|
required: ["operation"],
|
|
428
472
|
},
|
package/dist/types/cache.d.ts
CHANGED
package/dist/types/cache.js
CHANGED
package/docs/rtfm/adb.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ADB Tools
|
|
2
|
+
|
|
3
|
+
## adb-device
|
|
4
|
+
|
|
5
|
+
Manage device connections.
|
|
6
|
+
|
|
7
|
+
**Operations:**
|
|
8
|
+
- `list` - List connected devices
|
|
9
|
+
- `select` - Select active device
|
|
10
|
+
- `wait` - Wait for device to connect
|
|
11
|
+
- `properties` - Get device properties
|
|
12
|
+
|
|
13
|
+
## adb-app
|
|
14
|
+
|
|
15
|
+
Manage applications.
|
|
16
|
+
|
|
17
|
+
**Operations:**
|
|
18
|
+
- `install` - Install APK
|
|
19
|
+
- `uninstall` - Uninstall package
|
|
20
|
+
- `launch` - Launch app
|
|
21
|
+
- `stop` - Force stop app
|
|
22
|
+
- `clear-data` - Clear app data
|
|
23
|
+
|
|
24
|
+
## adb-logcat
|
|
25
|
+
|
|
26
|
+
Read device logs.
|
|
27
|
+
|
|
28
|
+
**Structured mode:**
|
|
29
|
+
- `package`: Filter to app's PID
|
|
30
|
+
- `tags`: Array of log tags
|
|
31
|
+
- `level`: "verbose" | "debug" | "info" | "warn" | "error"
|
|
32
|
+
|
|
33
|
+
**Raw mode:**
|
|
34
|
+
- `rawFilter`: Full logcat filter string (e.g., "ActivityManager:I MyApp:D *:S")
|
|
35
|
+
|
|
36
|
+
**Common:**
|
|
37
|
+
- `lines`: Number of lines (default: 100)
|
|
38
|
+
- `since`: Time filter ("5m" or ISO timestamp)
|
|
39
|
+
|
|
40
|
+
## adb-shell
|
|
41
|
+
|
|
42
|
+
Execute shell commands with safety guards.
|
|
43
|
+
|
|
44
|
+
**Parameters:**
|
|
45
|
+
- `command` (required): Shell command
|
|
46
|
+
- `timeout`: Max execution time (default: 30s, max: 120s)
|
|
47
|
+
|
|
48
|
+
**Blocked commands:** rm -rf /, reboot, shutdown, su, sudo
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Build Tools
|
|
2
|
+
|
|
3
|
+
## gradle-build
|
|
4
|
+
|
|
5
|
+
Build an Android application.
|
|
6
|
+
|
|
7
|
+
**Operations:**
|
|
8
|
+
- `assembleDebug` - Build debug APK
|
|
9
|
+
- `assembleRelease` - Build release APK
|
|
10
|
+
- `bundle` - Build Android App Bundle
|
|
11
|
+
|
|
12
|
+
**Parameters:**
|
|
13
|
+
- `operation` (required): Build operation
|
|
14
|
+
- `module`: Module path (e.g., ":app", ":feature:login")
|
|
15
|
+
- `flavor`: Product flavor name
|
|
16
|
+
|
|
17
|
+
**Returns:** `{ buildId, summary: { success, duration, apkSize, warnings } }`
|
|
18
|
+
|
|
19
|
+
**Example:**
|
|
20
|
+
```json
|
|
21
|
+
{ "operation": "assembleDebug", "module": ":app" }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## gradle-test
|
|
25
|
+
|
|
26
|
+
Run tests.
|
|
27
|
+
|
|
28
|
+
**Operations:**
|
|
29
|
+
- `unitTest` - Run unit tests
|
|
30
|
+
- `connectedTest` - Run instrumented tests on device
|
|
31
|
+
|
|
32
|
+
**Parameters:**
|
|
33
|
+
- `operation` (required): Test type
|
|
34
|
+
- `module`: Module path
|
|
35
|
+
- `filter`: Test filter (e.g., "com.example.MyTest", "*LoginTest*")
|
|
36
|
+
|
|
37
|
+
## gradle-list
|
|
38
|
+
|
|
39
|
+
Introspect project structure.
|
|
40
|
+
|
|
41
|
+
**Operations:**
|
|
42
|
+
- `variants` - List build variants
|
|
43
|
+
- `modules` - List project modules
|
|
44
|
+
- `tasks` - List Gradle tasks
|
|
45
|
+
|
|
46
|
+
## gradle-get-details
|
|
47
|
+
|
|
48
|
+
Fetch full output for a previous build/test.
|
|
49
|
+
|
|
50
|
+
**Parameters:**
|
|
51
|
+
- `id` (required): Build or test ID from previous operation
|
|
52
|
+
- `detailType`: "logs" | "errors" | "tasks"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Emulator Tools
|
|
2
|
+
|
|
3
|
+
## emulator-device
|
|
4
|
+
|
|
5
|
+
Manage Android emulators.
|
|
6
|
+
|
|
7
|
+
**Operations:**
|
|
8
|
+
- `list` - List available and running emulators
|
|
9
|
+
- `create` - Create new AVD
|
|
10
|
+
- `start` - Start emulator
|
|
11
|
+
- `kill` - Stop running emulator
|
|
12
|
+
- `wipe` - Reset emulator data
|
|
13
|
+
- `snapshot-save` - Save snapshot
|
|
14
|
+
- `snapshot-load` - Load snapshot
|
|
15
|
+
- `snapshot-list` - List snapshots
|
|
16
|
+
- `snapshot-delete` - Delete snapshot
|
|
17
|
+
|
|
18
|
+
**Parameters:**
|
|
19
|
+
- `operation` (required): Operation to perform
|
|
20
|
+
- `avdName`: AVD name (for create/start/wipe)
|
|
21
|
+
- `device`: Device profile (for create, e.g., "pixel_7")
|
|
22
|
+
- `systemImage`: System image (for create)
|
|
23
|
+
- `snapshotName`: Snapshot name (for snapshot operations)
|
package/docs/rtfm/ui.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# UI Automation Tools
|
|
2
|
+
|
|
3
|
+
## ui
|
|
4
|
+
|
|
5
|
+
Interact with app UI via accessibility tree with intelligent fallback.
|
|
6
|
+
|
|
7
|
+
**Operations:**
|
|
8
|
+
- `dump` - Get full accessibility tree
|
|
9
|
+
- `find` - Find elements by selector (with OCR/visual fallback)
|
|
10
|
+
- `tap` - Tap at coordinates, element index, or grid cell
|
|
11
|
+
- `input` - Enter text
|
|
12
|
+
- `screenshot` - Capture screen to file
|
|
13
|
+
- `accessibility-check` - Quick accessibility assessment
|
|
14
|
+
- `visual-snapshot` - Get screenshot + screen/app metadata
|
|
15
|
+
|
|
16
|
+
**Selectors (for find):**
|
|
17
|
+
- `resourceId`: Match resource ID (partial)
|
|
18
|
+
- `text`: Match exact text
|
|
19
|
+
- `textContains`: Match partial text
|
|
20
|
+
- `className`: Match class name
|
|
21
|
+
- `nearestTo`: Find elements nearest to this text (spatial proximity)
|
|
22
|
+
|
|
23
|
+
**Tap options:**
|
|
24
|
+
- `x`, `y`: Direct coordinates
|
|
25
|
+
- `elementIndex`: Index from previous find result
|
|
26
|
+
- `gridCell`: Grid cell 1-24 (6x4 grid overlay)
|
|
27
|
+
- `gridPosition`: Position within cell (1=TL, 2=TR, 3=Center, 4=BL, 5=BR)
|
|
28
|
+
|
|
29
|
+
**Optional parameters:**
|
|
30
|
+
- `debug`: Include source (accessibility/ocr) and confidence scores
|
|
31
|
+
- `inline`: Return base64 screenshot in response (for screenshot op)
|
|
32
|
+
- `localPath`: Custom path for screenshot output
|
|
33
|
+
|
|
34
|
+
**Fallback chain:**
|
|
35
|
+
1. Accessibility tree (fast, reliable)
|
|
36
|
+
2. OCR via Tesseract (when accessibility fails)
|
|
37
|
+
3. Visual snapshot (screenshot + metadata for AI vision)
|
|
38
|
+
|
|
39
|
+
**Example - Find and tap:**
|
|
40
|
+
```json
|
|
41
|
+
{ "operation": "find", "selector": { "text": "Login" } }
|
|
42
|
+
// Returns: { elements: [{ index: 0, centerX: 540, centerY: 1200, ... }] }
|
|
43
|
+
|
|
44
|
+
{ "operation": "tap", "elementIndex": 0 }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Example - Spatial proximity:**
|
|
48
|
+
```json
|
|
49
|
+
{ "operation": "find", "selector": { "textContains": "edit", "nearestTo": "John" } }
|
|
50
|
+
// Returns elements containing "edit", sorted by distance to "John"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Example - Grid-based tap (for icons):**
|
|
54
|
+
```json
|
|
55
|
+
{ "operation": "tap", "gridCell": 12, "gridPosition": 3 }
|
|
56
|
+
// Taps center of cell 12 in the 24-cell grid overlay
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Screenshot Scaling
|
|
60
|
+
|
|
61
|
+
Screenshots are automatically scaled to fit within 1000px (longest side) by default.
|
|
62
|
+
This prevents API context limits and reduces token usage.
|
|
63
|
+
|
|
64
|
+
**All coordinates are in image space.** Tap coordinates are automatically converted
|
|
65
|
+
to device coordinates. You don't need to do any math.
|
|
66
|
+
|
|
67
|
+
### Scaling Modes
|
|
68
|
+
|
|
69
|
+
| Mode | Parameter | Behavior |
|
|
70
|
+
|------|-----------|----------|
|
|
71
|
+
| Default | (none) | Scale to 1000px max |
|
|
72
|
+
| Custom | `maxDimension: 1500` | Scale to specified size |
|
|
73
|
+
| Raw | `raw: true` | No scaling (may exceed API limits) |
|
|
74
|
+
|
|
75
|
+
### When to Use Raw Mode
|
|
76
|
+
|
|
77
|
+
- Non-Anthropic models with different limits
|
|
78
|
+
- External context management (compaction, agent respawning)
|
|
79
|
+
- Debugging coordinate issues
|
|
80
|
+
|
|
81
|
+
### Response Format
|
|
82
|
+
|
|
83
|
+
Screenshot responses now include scaling metadata:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mode": "file",
|
|
88
|
+
"path": ".replicant/screenshots/screenshot-1234.png",
|
|
89
|
+
"device": { "width": 1080, "height": 2400 },
|
|
90
|
+
"image": { "width": 450, "height": 1000 },
|
|
91
|
+
"scaleFactor": 2.4
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Context Management
|
|
96
|
+
|
|
97
|
+
**Prefer accessibility tree (`dump`, `find`) because:**
|
|
98
|
+
- No context cost (text, not images)
|
|
99
|
+
- Coordinates are precise
|
|
100
|
+
- Faster execution
|
|
101
|
+
|
|
102
|
+
**Use screenshots when:**
|
|
103
|
+
- Accessibility tree is empty/unhelpful
|
|
104
|
+
- You need to see visual layout
|
|
105
|
+
- Icons have no text labels
|
|
106
|
+
|
|
107
|
+
**Ask yourself:** Do I need to SEE the screen, or just INTERACT with it?
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replicant-mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "Android MCP server for AI-assisted Android development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
46
|
"dist/",
|
|
47
|
+
"docs/rtfm/",
|
|
47
48
|
"README.md",
|
|
48
49
|
"LICENSE"
|
|
49
50
|
],
|