replicant-mcp 1.0.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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +386 -0
  3. package/dist/adapters/adb.d.ts +21 -0
  4. package/dist/adapters/adb.js +75 -0
  5. package/dist/adapters/emulator.d.ts +19 -0
  6. package/dist/adapters/emulator.js +72 -0
  7. package/dist/adapters/gradle.d.ts +20 -0
  8. package/dist/adapters/gradle.js +80 -0
  9. package/dist/adapters/index.d.ts +4 -0
  10. package/dist/adapters/index.js +4 -0
  11. package/dist/adapters/ui-automator.d.ts +23 -0
  12. package/dist/adapters/ui-automator.js +53 -0
  13. package/dist/cli/adb.d.ts +2 -0
  14. package/dist/cli/adb.js +256 -0
  15. package/dist/cli/cache.d.ts +2 -0
  16. package/dist/cli/cache.js +115 -0
  17. package/dist/cli/emulator.d.ts +2 -0
  18. package/dist/cli/emulator.js +181 -0
  19. package/dist/cli/formatter.d.ts +52 -0
  20. package/dist/cli/formatter.js +68 -0
  21. package/dist/cli/gradle.d.ts +2 -0
  22. package/dist/cli/gradle.js +192 -0
  23. package/dist/cli/index.d.ts +6 -0
  24. package/dist/cli/index.js +6 -0
  25. package/dist/cli/ui.d.ts +2 -0
  26. package/dist/cli/ui.js +218 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +14 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +6 -0
  31. package/dist/parsers/adb-output.d.ts +4 -0
  32. package/dist/parsers/adb-output.js +32 -0
  33. package/dist/parsers/emulator-output.d.ts +9 -0
  34. package/dist/parsers/emulator-output.js +33 -0
  35. package/dist/parsers/gradle-output.d.ts +30 -0
  36. package/dist/parsers/gradle-output.js +80 -0
  37. package/dist/parsers/index.d.ts +4 -0
  38. package/dist/parsers/index.js +4 -0
  39. package/dist/parsers/ui-dump.d.ts +27 -0
  40. package/dist/parsers/ui-dump.js +142 -0
  41. package/dist/server.d.ts +15 -0
  42. package/dist/server.js +113 -0
  43. package/dist/services/cache-manager.d.ts +22 -0
  44. package/dist/services/cache-manager.js +90 -0
  45. package/dist/services/device-state.d.ts +9 -0
  46. package/dist/services/device-state.js +26 -0
  47. package/dist/services/index.d.ts +3 -0
  48. package/dist/services/index.js +3 -0
  49. package/dist/services/process-runner.d.ts +15 -0
  50. package/dist/services/process-runner.js +62 -0
  51. package/dist/tools/adb-app.d.ts +38 -0
  52. package/dist/tools/adb-app.js +68 -0
  53. package/dist/tools/adb-device.d.ts +31 -0
  54. package/dist/tools/adb-device.js +71 -0
  55. package/dist/tools/adb-logcat.d.ts +54 -0
  56. package/dist/tools/adb-logcat.js +70 -0
  57. package/dist/tools/adb-shell.d.ts +26 -0
  58. package/dist/tools/adb-shell.js +27 -0
  59. package/dist/tools/cache.d.ts +50 -0
  60. package/dist/tools/cache.js +57 -0
  61. package/dist/tools/emulator-device.d.ts +56 -0
  62. package/dist/tools/emulator-device.js +132 -0
  63. package/dist/tools/gradle-build.d.ts +35 -0
  64. package/dist/tools/gradle-build.js +40 -0
  65. package/dist/tools/gradle-get-details.d.ts +32 -0
  66. package/dist/tools/gradle-get-details.js +72 -0
  67. package/dist/tools/gradle-list.d.ts +30 -0
  68. package/dist/tools/gradle-list.js +55 -0
  69. package/dist/tools/gradle-test.d.ts +34 -0
  70. package/dist/tools/gradle-test.js +40 -0
  71. package/dist/tools/index.d.ts +12 -0
  72. package/dist/tools/index.js +12 -0
  73. package/dist/tools/rtfm.d.ts +26 -0
  74. package/dist/tools/rtfm.js +70 -0
  75. package/dist/tools/ui.d.ts +77 -0
  76. package/dist/tools/ui.js +131 -0
  77. package/dist/types/cache.d.ts +24 -0
  78. package/dist/types/cache.js +14 -0
  79. package/dist/types/device.d.ts +11 -0
  80. package/dist/types/device.js +1 -0
  81. package/dist/types/errors.d.ts +31 -0
  82. package/dist/types/errors.js +43 -0
  83. package/dist/types/index.d.ts +3 -0
  84. package/dist/types/index.js +3 -0
  85. package/package.json +64 -0
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ import { CACHE_TTLS } from "../types/index.js";
3
+ export const gradleListInputSchema = z.object({
4
+ operation: z.enum(["variants", "modules", "tasks"]),
5
+ module: z.string().optional(),
6
+ });
7
+ export async function handleGradleListTool(input, context) {
8
+ switch (input.operation) {
9
+ case "modules": {
10
+ const modules = await context.gradle.listModules();
11
+ return { modules };
12
+ }
13
+ case "variants": {
14
+ const variants = await context.gradle.listVariants(input.module);
15
+ return { variants, module: input.module || "all" };
16
+ }
17
+ case "tasks": {
18
+ const tasks = await context.gradle.listTasks(input.module);
19
+ // Cache full task list
20
+ const listId = context.cache.generateId("tasks");
21
+ context.cache.set(listId, { tasks }, "tasks", CACHE_TTLS.GRADLE_VARIANTS);
22
+ // Return categorized summary
23
+ const buildTasks = tasks.filter((t) => t.includes("assemble") || t.includes("bundle"));
24
+ const testTasks = tasks.filter((t) => t.includes("test") || t.includes("Test"));
25
+ const cleanTasks = tasks.filter((t) => t.includes("clean"));
26
+ return {
27
+ listId,
28
+ summary: {
29
+ totalTasks: tasks.length,
30
+ buildTasks: buildTasks.slice(0, 10),
31
+ testTasks: testTasks.slice(0, 10),
32
+ cleanTasks: cleanTasks.slice(0, 5),
33
+ },
34
+ module: input.module || "all",
35
+ };
36
+ }
37
+ default:
38
+ throw new Error(`Unknown operation: ${input.operation}`);
39
+ }
40
+ }
41
+ export const gradleListToolDefinition = {
42
+ name: "gradle-list",
43
+ description: "Introspect project structure. Operations: modules, variants, tasks.",
44
+ inputSchema: {
45
+ type: "object",
46
+ properties: {
47
+ operation: {
48
+ type: "string",
49
+ enum: ["variants", "modules", "tasks"],
50
+ },
51
+ module: { type: "string", description: "Module path (for variants/tasks)" },
52
+ },
53
+ required: ["operation"],
54
+ },
55
+ };
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import { ServerContext } from "../server.js";
3
+ export declare const gradleTestInputSchema: z.ZodObject<{
4
+ operation: z.ZodEnum<{
5
+ unitTest: "unitTest";
6
+ connectedTest: "connectedTest";
7
+ }>;
8
+ module: z.ZodOptional<z.ZodString>;
9
+ filter: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strip>;
11
+ export type GradleTestInput = z.infer<typeof gradleTestInputSchema>;
12
+ export declare function handleGradleTestTool(input: GradleTestInput, context: ServerContext): Promise<Record<string, unknown>>;
13
+ export declare const gradleTestToolDefinition: {
14
+ name: string;
15
+ description: string;
16
+ inputSchema: {
17
+ type: string;
18
+ properties: {
19
+ operation: {
20
+ type: string;
21
+ enum: string[];
22
+ };
23
+ module: {
24
+ type: string;
25
+ description: string;
26
+ };
27
+ filter: {
28
+ type: string;
29
+ description: string;
30
+ };
31
+ };
32
+ required: string[];
33
+ };
34
+ };
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ import { CACHE_TTLS } from "../types/index.js";
3
+ export const gradleTestInputSchema = z.object({
4
+ operation: z.enum(["unitTest", "connectedTest"]),
5
+ module: z.string().optional(),
6
+ filter: z.string().optional(),
7
+ });
8
+ export async function handleGradleTestTool(input, context) {
9
+ const { result, fullOutput } = await context.gradle.test(input.operation, input.module, input.filter);
10
+ // Cache full output for later retrieval
11
+ const testId = context.cache.generateId("test");
12
+ context.cache.set(testId, { fullOutput, result, operation: input.operation }, "test", CACHE_TTLS.TEST_RESULTS);
13
+ return {
14
+ testId,
15
+ summary: {
16
+ passed: result.passed,
17
+ failed: result.failed,
18
+ skipped: result.skipped,
19
+ total: result.total,
20
+ duration: result.duration,
21
+ },
22
+ failures: result.failures,
23
+ };
24
+ }
25
+ export const gradleTestToolDefinition = {
26
+ name: "gradle-test",
27
+ description: "Run tests. Operations: unitTest, connectedTest. Returns summary with testId.",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ operation: {
32
+ type: "string",
33
+ enum: ["unitTest", "connectedTest"],
34
+ },
35
+ module: { type: "string", description: "Module path" },
36
+ filter: { type: "string", description: "Test filter (e.g., '*LoginTest*')" },
37
+ },
38
+ required: ["operation"],
39
+ },
40
+ };
@@ -0,0 +1,12 @@
1
+ export * from "./cache.js";
2
+ export * from "./rtfm.js";
3
+ export * from "./adb-device.js";
4
+ export * from "./adb-app.js";
5
+ export * from "./adb-logcat.js";
6
+ export * from "./adb-shell.js";
7
+ export * from "./emulator-device.js";
8
+ export * from "./gradle-build.js";
9
+ export * from "./gradle-test.js";
10
+ export * from "./gradle-list.js";
11
+ export * from "./gradle-get-details.js";
12
+ export * from "./ui.js";
@@ -0,0 +1,12 @@
1
+ export * from "./cache.js";
2
+ export * from "./rtfm.js";
3
+ export * from "./adb-device.js";
4
+ export * from "./adb-app.js";
5
+ export * from "./adb-logcat.js";
6
+ export * from "./adb-shell.js";
7
+ export * from "./emulator-device.js";
8
+ export * from "./gradle-build.js";
9
+ export * from "./gradle-test.js";
10
+ export * from "./gradle-list.js";
11
+ export * from "./gradle-get-details.js";
12
+ export * from "./ui.js";
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ export declare const rtfmInputSchema: z.ZodObject<{
3
+ category: z.ZodOptional<z.ZodString>;
4
+ tool: z.ZodOptional<z.ZodString>;
5
+ }, z.core.$strip>;
6
+ export type RtfmInput = z.infer<typeof rtfmInputSchema>;
7
+ export declare function handleRtfmTool(input: RtfmInput): Promise<{
8
+ content: string;
9
+ }>;
10
+ export declare const rtfmToolDefinition: {
11
+ name: string;
12
+ description: string;
13
+ inputSchema: {
14
+ type: string;
15
+ properties: {
16
+ category: {
17
+ type: string;
18
+ description: string;
19
+ };
20
+ tool: {
21
+ type: string;
22
+ description: string;
23
+ };
24
+ };
25
+ };
26
+ };
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ import { readFile } from "fs/promises";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const RTFM_DIR = join(__dirname, "../../docs/rtfm");
7
+ export const rtfmInputSchema = z.object({
8
+ category: z.string().optional(),
9
+ tool: z.string().optional(),
10
+ });
11
+ export async function handleRtfmTool(input) {
12
+ if (!input.category && !input.tool) {
13
+ // Return index
14
+ const content = await readFile(join(RTFM_DIR, "index.md"), "utf-8");
15
+ return { content };
16
+ }
17
+ if (input.category) {
18
+ try {
19
+ const content = await readFile(join(RTFM_DIR, `${input.category}.md`), "utf-8");
20
+ return { content };
21
+ }
22
+ catch {
23
+ return { content: `Category '${input.category}' not found. Available: build, adb, emulator, ui` };
24
+ }
25
+ }
26
+ if (input.tool) {
27
+ // Map tool to category
28
+ const toolToCategory = {
29
+ "gradle-build": "build",
30
+ "gradle-test": "build",
31
+ "gradle-list": "build",
32
+ "gradle-get-details": "build",
33
+ "adb-device": "adb",
34
+ "adb-app": "adb",
35
+ "adb-logcat": "adb",
36
+ "adb-shell": "adb",
37
+ "emulator-device": "emulator",
38
+ "ui": "ui",
39
+ "cache": "build",
40
+ "rtfm": "build",
41
+ };
42
+ const category = toolToCategory[input.tool] || "index";
43
+ try {
44
+ const content = await readFile(join(RTFM_DIR, `${category}.md`), "utf-8");
45
+ // Try to extract just the relevant section
46
+ const toolSection = extractToolSection(content, input.tool);
47
+ return { content: toolSection || content };
48
+ }
49
+ catch {
50
+ return { content: `Tool '${input.tool}' not found.` };
51
+ }
52
+ }
53
+ return { content: "No documentation found." };
54
+ }
55
+ function extractToolSection(content, toolName) {
56
+ const regex = new RegExp(`## ${toolName}[\\s\\S]*?(?=## |$)`, "i");
57
+ const match = content.match(regex);
58
+ return match ? match[0].trim() : null;
59
+ }
60
+ export const rtfmToolDefinition = {
61
+ name: "rtfm",
62
+ description: "Get documentation. Pass category or tool name.",
63
+ inputSchema: {
64
+ type: "object",
65
+ properties: {
66
+ category: { type: "string", description: "Category: build, adb, emulator, ui" },
67
+ tool: { type: "string", description: "Tool name for specific docs" },
68
+ },
69
+ },
70
+ };
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ import { ServerContext } from "../server.js";
3
+ export declare const uiInputSchema: z.ZodObject<{
4
+ operation: z.ZodEnum<{
5
+ find: "find";
6
+ dump: "dump";
7
+ tap: "tap";
8
+ input: "input";
9
+ screenshot: "screenshot";
10
+ "accessibility-check": "accessibility-check";
11
+ }>;
12
+ selector: z.ZodOptional<z.ZodObject<{
13
+ resourceId: z.ZodOptional<z.ZodString>;
14
+ text: z.ZodOptional<z.ZodString>;
15
+ textContains: z.ZodOptional<z.ZodString>;
16
+ className: z.ZodOptional<z.ZodString>;
17
+ }, z.core.$strip>>;
18
+ x: z.ZodOptional<z.ZodNumber>;
19
+ y: z.ZodOptional<z.ZodNumber>;
20
+ elementIndex: z.ZodOptional<z.ZodNumber>;
21
+ text: z.ZodOptional<z.ZodString>;
22
+ localPath: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>;
24
+ export type UiInput = z.infer<typeof uiInputSchema>;
25
+ export declare function handleUiTool(input: UiInput, context: ServerContext): Promise<Record<string, unknown>>;
26
+ export declare const uiToolDefinition: {
27
+ name: string;
28
+ description: string;
29
+ inputSchema: {
30
+ type: string;
31
+ properties: {
32
+ operation: {
33
+ type: string;
34
+ enum: string[];
35
+ };
36
+ selector: {
37
+ type: string;
38
+ properties: {
39
+ resourceId: {
40
+ type: string;
41
+ };
42
+ text: {
43
+ type: string;
44
+ };
45
+ textContains: {
46
+ type: string;
47
+ };
48
+ className: {
49
+ type: string;
50
+ };
51
+ };
52
+ description: string;
53
+ };
54
+ x: {
55
+ type: string;
56
+ description: string;
57
+ };
58
+ y: {
59
+ type: string;
60
+ description: string;
61
+ };
62
+ elementIndex: {
63
+ type: string;
64
+ description: string;
65
+ };
66
+ text: {
67
+ type: string;
68
+ description: string;
69
+ };
70
+ localPath: {
71
+ type: string;
72
+ description: string;
73
+ };
74
+ };
75
+ required: string[];
76
+ };
77
+ };
@@ -0,0 +1,131 @@
1
+ import { z } from "zod";
2
+ import { CACHE_TTLS } from "../types/index.js";
3
+ export const uiInputSchema = z.object({
4
+ operation: z.enum(["dump", "find", "tap", "input", "screenshot", "accessibility-check"]),
5
+ selector: z.object({
6
+ resourceId: z.string().optional(),
7
+ text: z.string().optional(),
8
+ textContains: z.string().optional(),
9
+ className: z.string().optional(),
10
+ }).optional(),
11
+ x: z.number().optional(),
12
+ y: z.number().optional(),
13
+ elementIndex: z.number().optional(),
14
+ text: z.string().optional(),
15
+ localPath: z.string().optional(),
16
+ });
17
+ // Store last find results for elementIndex reference
18
+ let lastFindResults = [];
19
+ export async function handleUiTool(input, context) {
20
+ const deviceId = context.deviceState.requireCurrentDevice().id;
21
+ switch (input.operation) {
22
+ case "dump": {
23
+ const tree = await context.ui.dump(deviceId);
24
+ // Cache the tree
25
+ const dumpId = context.cache.generateId("ui-dump");
26
+ context.cache.set(dumpId, { tree, deviceId }, "ui-dump", CACHE_TTLS.UI_TREE);
27
+ // Create a simplified view
28
+ const simplifyNode = (node, depth = 0) => ({
29
+ className: node.className.split(".").pop(),
30
+ text: node.text || undefined,
31
+ resourceId: node.resourceId ? node.resourceId.split("/").pop() : undefined,
32
+ bounds: `[${node.bounds.left},${node.bounds.top}][${node.bounds.right},${node.bounds.bottom}]`,
33
+ clickable: node.clickable || undefined,
34
+ children: node.children?.map((c) => simplifyNode(c, depth + 1)),
35
+ });
36
+ return {
37
+ dumpId,
38
+ tree: tree.map((n) => simplifyNode(n)),
39
+ deviceId,
40
+ };
41
+ }
42
+ case "find": {
43
+ if (!input.selector) {
44
+ throw new Error("selector is required for find operation");
45
+ }
46
+ const elements = await context.ui.find(deviceId, input.selector);
47
+ lastFindResults = elements;
48
+ return {
49
+ elements: elements.map((el, index) => ({
50
+ index,
51
+ text: el.text,
52
+ resourceId: el.resourceId,
53
+ className: el.className,
54
+ centerX: el.centerX,
55
+ centerY: el.centerY,
56
+ bounds: el.bounds,
57
+ clickable: el.clickable,
58
+ })),
59
+ count: elements.length,
60
+ deviceId,
61
+ };
62
+ }
63
+ case "tap": {
64
+ let x, y;
65
+ if (input.elementIndex !== undefined) {
66
+ if (!lastFindResults[input.elementIndex]) {
67
+ throw new Error(`Element at index ${input.elementIndex} not found. Run 'find' first.`);
68
+ }
69
+ const element = lastFindResults[input.elementIndex];
70
+ x = element.centerX;
71
+ y = element.centerY;
72
+ }
73
+ else if (input.x !== undefined && input.y !== undefined) {
74
+ x = input.x;
75
+ y = input.y;
76
+ }
77
+ else {
78
+ throw new Error("Either x/y coordinates or elementIndex is required for tap");
79
+ }
80
+ await context.ui.tap(deviceId, x, y);
81
+ return { tapped: { x, y }, deviceId };
82
+ }
83
+ case "input": {
84
+ if (!input.text) {
85
+ throw new Error("text is required for input operation");
86
+ }
87
+ await context.ui.input(deviceId, input.text);
88
+ return { input: input.text, deviceId };
89
+ }
90
+ case "screenshot": {
91
+ const localPath = input.localPath || `/tmp/screenshot-${Date.now()}.png`;
92
+ await context.ui.screenshot(deviceId, localPath);
93
+ return { path: localPath, deviceId };
94
+ }
95
+ case "accessibility-check": {
96
+ const result = await context.ui.accessibilityCheck(deviceId);
97
+ return { ...result, deviceId };
98
+ }
99
+ default:
100
+ throw new Error(`Unknown operation: ${input.operation}`);
101
+ }
102
+ }
103
+ export const uiToolDefinition = {
104
+ name: "ui",
105
+ description: "Interact with app UI via accessibility tree. Operations: dump, find, tap, input, screenshot, accessibility-check.",
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {
109
+ operation: {
110
+ type: "string",
111
+ enum: ["dump", "find", "tap", "input", "screenshot", "accessibility-check"],
112
+ },
113
+ selector: {
114
+ type: "object",
115
+ properties: {
116
+ resourceId: { type: "string" },
117
+ text: { type: "string" },
118
+ textContains: { type: "string" },
119
+ className: { type: "string" },
120
+ },
121
+ description: "Element selector (for find)",
122
+ },
123
+ x: { type: "number", description: "X coordinate (for tap)" },
124
+ y: { type: "number", description: "Y coordinate (for tap)" },
125
+ elementIndex: { type: "number", description: "Element index from last find (for tap)" },
126
+ text: { type: "string", description: "Text to input" },
127
+ localPath: { type: "string", description: "Local path for screenshot" },
128
+ },
129
+ required: ["operation"],
130
+ },
131
+ };
@@ -0,0 +1,24 @@
1
+ export interface CacheEntry<T = unknown> {
2
+ data: T;
3
+ expiresAt: number;
4
+ metadata: {
5
+ createdAt: number;
6
+ type: string;
7
+ sizeBytes?: number;
8
+ };
9
+ }
10
+ export interface CacheConfig {
11
+ maxEntries: number;
12
+ maxEntrySizeBytes: number;
13
+ defaultTtlMs: number;
14
+ }
15
+ export declare const DEFAULT_CACHE_CONFIG: CacheConfig;
16
+ export declare const CACHE_TTLS: {
17
+ readonly BUILD_OUTPUT: number;
18
+ readonly TEST_RESULTS: number;
19
+ readonly EMULATOR_LIST: number;
20
+ readonly APP_LIST: number;
21
+ readonly UI_TREE: number;
22
+ readonly GRADLE_VARIANTS: number;
23
+ readonly LOGCAT: number;
24
+ };
@@ -0,0 +1,14 @@
1
+ export const DEFAULT_CACHE_CONFIG = {
2
+ maxEntries: 100,
3
+ maxEntrySizeBytes: 1024 * 1024, // 1MB
4
+ defaultTtlMs: 5 * 60 * 1000, // 5 minutes
5
+ };
6
+ export const CACHE_TTLS = {
7
+ BUILD_OUTPUT: 30 * 60 * 1000, // 30 min
8
+ TEST_RESULTS: 30 * 60 * 1000, // 30 min
9
+ EMULATOR_LIST: 5 * 60 * 1000, // 5 min
10
+ APP_LIST: 2 * 60 * 1000, // 2 min
11
+ UI_TREE: 30 * 1000, // 30 sec
12
+ GRADLE_VARIANTS: 60 * 60 * 1000, // 1 hour
13
+ LOGCAT: 5 * 60 * 1000, // 5 min
14
+ };
@@ -0,0 +1,11 @@
1
+ export type DeviceType = "emulator" | "physical";
2
+ export type DeviceStatus = "online" | "offline" | "booting";
3
+ export interface Device {
4
+ id: string;
5
+ type: DeviceType;
6
+ name: string;
7
+ status: DeviceStatus;
8
+ }
9
+ export interface DeviceState {
10
+ currentDevice: Device | null;
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ export declare const ErrorCode: {
2
+ readonly NO_DEVICE_SELECTED: "NO_DEVICE_SELECTED";
3
+ readonly DEVICE_NOT_FOUND: "DEVICE_NOT_FOUND";
4
+ readonly DEVICE_OFFLINE: "DEVICE_OFFLINE";
5
+ readonly BUILD_FAILED: "BUILD_FAILED";
6
+ readonly GRADLE_NOT_FOUND: "GRADLE_NOT_FOUND";
7
+ readonly MODULE_NOT_FOUND: "MODULE_NOT_FOUND";
8
+ readonly APK_NOT_FOUND: "APK_NOT_FOUND";
9
+ readonly PACKAGE_NOT_FOUND: "PACKAGE_NOT_FOUND";
10
+ readonly INSTALL_FAILED: "INSTALL_FAILED";
11
+ readonly AVD_NOT_FOUND: "AVD_NOT_FOUND";
12
+ readonly EMULATOR_START_FAILED: "EMULATOR_START_FAILED";
13
+ readonly SNAPSHOT_NOT_FOUND: "SNAPSHOT_NOT_FOUND";
14
+ readonly COMMAND_BLOCKED: "COMMAND_BLOCKED";
15
+ readonly TIMEOUT: "TIMEOUT";
16
+ readonly CACHE_MISS: "CACHE_MISS";
17
+ };
18
+ export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
19
+ export interface ToolError {
20
+ error: ErrorCode;
21
+ message: string;
22
+ suggestion?: string;
23
+ details?: Record<string, unknown>;
24
+ }
25
+ export declare class ReplicantError extends Error {
26
+ readonly code: ErrorCode;
27
+ readonly suggestion?: string | undefined;
28
+ readonly details?: Record<string, unknown> | undefined;
29
+ constructor(code: ErrorCode, message: string, suggestion?: string | undefined, details?: Record<string, unknown> | undefined);
30
+ toToolError(): ToolError;
31
+ }
@@ -0,0 +1,43 @@
1
+ export const ErrorCode = {
2
+ // Device errors
3
+ NO_DEVICE_SELECTED: "NO_DEVICE_SELECTED",
4
+ DEVICE_NOT_FOUND: "DEVICE_NOT_FOUND",
5
+ DEVICE_OFFLINE: "DEVICE_OFFLINE",
6
+ // Build errors
7
+ BUILD_FAILED: "BUILD_FAILED",
8
+ GRADLE_NOT_FOUND: "GRADLE_NOT_FOUND",
9
+ MODULE_NOT_FOUND: "MODULE_NOT_FOUND",
10
+ // App errors
11
+ APK_NOT_FOUND: "APK_NOT_FOUND",
12
+ PACKAGE_NOT_FOUND: "PACKAGE_NOT_FOUND",
13
+ INSTALL_FAILED: "INSTALL_FAILED",
14
+ // Emulator errors
15
+ AVD_NOT_FOUND: "AVD_NOT_FOUND",
16
+ EMULATOR_START_FAILED: "EMULATOR_START_FAILED",
17
+ SNAPSHOT_NOT_FOUND: "SNAPSHOT_NOT_FOUND",
18
+ // Safety errors
19
+ COMMAND_BLOCKED: "COMMAND_BLOCKED",
20
+ TIMEOUT: "TIMEOUT",
21
+ // Cache errors
22
+ CACHE_MISS: "CACHE_MISS",
23
+ };
24
+ export class ReplicantError extends Error {
25
+ code;
26
+ suggestion;
27
+ details;
28
+ constructor(code, message, suggestion, details) {
29
+ super(message);
30
+ this.code = code;
31
+ this.suggestion = suggestion;
32
+ this.details = details;
33
+ this.name = "ReplicantError";
34
+ }
35
+ toToolError() {
36
+ return {
37
+ error: this.code,
38
+ message: this.message,
39
+ suggestion: this.suggestion,
40
+ details: this.details,
41
+ };
42
+ }
43
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./errors.js";
2
+ export * from "./cache.js";
3
+ export * from "./device.js";
@@ -0,0 +1,3 @@
1
+ export * from "./errors.js";
2
+ export * from "./cache.js";
3
+ export * from "./device.js";
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "replicant-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Android MCP server for AI-assisted Android development",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "replicant-mcp": "dist/index.js",
9
+ "replicant": "dist/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "test": "vitest",
15
+ "test:unit": "vitest --run tests/services tests/adapters tests/tools",
16
+ "test:integration": "vitest --run tests/integration",
17
+ "test:coverage": "vitest --run --coverage",
18
+ "check-prereqs": "bash scripts/check-prerequisites.sh",
19
+ "smoke-test": "bash scripts/smoke-test.sh",
20
+ "test:device": "tsx scripts/real-device-test.ts",
21
+ "start": "node dist/index.js",
22
+ "validate": "npm run build && npm run test -- --run",
23
+ "install-skill": "bash scripts/install-skill.sh",
24
+ "prepublishOnly": "npm run build && npm test -- --run"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "android",
30
+ "adb",
31
+ "gradle",
32
+ "emulator",
33
+ "ui-automation",
34
+ "ai",
35
+ "llm"
36
+ ],
37
+ "author": "Archit Joshi",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/thecombatwombat/replicant-mcp.git"
42
+ },
43
+ "files": [
44
+ "dist/",
45
+ "README.md",
46
+ "LICENSE"
47
+ ],
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "dependencies": {
52
+ "@modelcontextprotocol/sdk": "^1.25.3",
53
+ "commander": "^14.0.2",
54
+ "execa": "^9.6.1",
55
+ "zod": "^4.3.5"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^25.0.9",
59
+ "@vitest/coverage-v8": "^4.0.17",
60
+ "tsx": "^4.21.0",
61
+ "typescript": "^5.9.3",
62
+ "vitest": "^4.0.17"
63
+ }
64
+ }