@sensaiorg/adapter-android 0.1.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.
- package/dist/android-adapter.d.ts.map +1 -0
- package/dist/android-adapter.js +89 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/tools/accessibility.d.ts.map +1 -0
- package/dist/tools/accessibility.js +85 -0
- package/dist/tools/adb.d.ts.map +1 -0
- package/dist/tools/adb.js +66 -0
- package/dist/tools/app-state.d.ts.map +1 -0
- package/dist/tools/app-state.js +173 -0
- package/dist/tools/diagnose.d.ts.map +1 -0
- package/dist/tools/diagnose.js +128 -0
- package/dist/tools/hot-reload.d.ts.map +1 -0
- package/dist/tools/hot-reload.js +97 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +66 -0
- package/dist/tools/interaction.d.ts.map +1 -0
- package/dist/tools/interaction.js +395 -0
- package/dist/tools/logcat.d.ts.map +1 -0
- package/dist/tools/logcat.js +216 -0
- package/dist/tools/network.d.ts.map +1 -0
- package/dist/tools/network.js +123 -0
- package/dist/tools/performance.d.ts.map +1 -0
- package/dist/tools/performance.js +143 -0
- package/dist/tools/recording.d.ts.map +1 -0
- package/dist/tools/recording.js +102 -0
- package/dist/tools/rn-tools.d.ts.map +1 -0
- package/dist/tools/rn-tools.js +120 -0
- package/dist/tools/smart-actions.d.ts.map +1 -0
- package/dist/tools/smart-actions.js +506 -0
- package/dist/tools/ui-tree.d.ts.map +1 -0
- package/dist/tools/ui-tree.js +226 -0
- package/dist/transport/adb-client.d.ts.map +1 -0
- package/dist/transport/adb-client.js +124 -0
- package/dist/transport/adb-client.test.d.ts.map +1 -0
- package/dist/transport/adb-client.test.js +153 -0
- package/dist/transport/agent-client.d.ts.map +1 -0
- package/dist/transport/agent-client.js +157 -0
- package/dist/transport/agent-client.test.d.ts.map +1 -0
- package/dist/transport/agent-client.test.js +199 -0
- package/dist/transport/connection-manager.d.ts.map +1 -0
- package/dist/transport/connection-manager.js +119 -0
- package/dist/util/logcat-parser.d.ts.map +1 -0
- package/dist/util/logcat-parser.js +79 -0
- package/dist/util/safety.d.ts.map +1 -0
- package/dist/util/safety.js +132 -0
- package/dist/util/safety.test.d.ts.map +1 -0
- package/dist/util/safety.test.js +205 -0
- package/dist/util/text-extractor.d.ts.map +1 -0
- package/dist/util/text-extractor.js +71 -0
- package/dist/util/ui-tree-cache.d.ts.map +1 -0
- package/dist/util/ui-tree-cache.js +46 -0
- package/dist/util/ui-tree-cache.test.d.ts.map +1 -0
- package/dist/util/ui-tree-cache.test.js +84 -0
- package/dist/util/ui-tree-parser.d.ts.map +1 -0
- package/dist/util/ui-tree-parser.js +123 -0
- package/dist/util/ui-tree-parser.test.d.ts.map +1 -0
- package/dist/util/ui-tree-parser.test.js +167 -0
- package/package.json +22 -0
- package/src/android-adapter.ts +124 -0
- package/src/index.ts +8 -0
- package/src/tools/accessibility.ts +94 -0
- package/src/tools/adb.ts +75 -0
- package/src/tools/app-state.ts +193 -0
- package/src/tools/diagnose.ts +146 -0
- package/src/tools/hot-reload.ts +103 -0
- package/src/tools/index.ts +66 -0
- package/src/tools/interaction.ts +448 -0
- package/src/tools/logcat.ts +252 -0
- package/src/tools/network.ts +145 -0
- package/src/tools/performance.ts +169 -0
- package/src/tools/recording.ts +123 -0
- package/src/tools/rn-tools.ts +143 -0
- package/src/tools/smart-actions.ts +593 -0
- package/src/tools/ui-tree.ts +258 -0
- package/src/transport/adb-client.test.ts +228 -0
- package/src/transport/adb-client.ts +139 -0
- package/src/transport/agent-client.test.ts +267 -0
- package/src/transport/agent-client.ts +188 -0
- package/src/transport/connection-manager.ts +140 -0
- package/src/util/logcat-parser.ts +94 -0
- package/src/util/safety.test.ts +251 -0
- package/src/util/safety.ts +143 -0
- package/src/util/text-extractor.ts +87 -0
- package/src/util/ui-tree-cache.test.ts +105 -0
- package/src/util/ui-tree-cache.ts +54 -0
- package/src/util/ui-tree-parser.test.ts +182 -0
- package/src/util/ui-tree-parser.ts +169 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnose Tool - Composite screen diagnosis.
|
|
3
|
+
*
|
|
4
|
+
* Combines multiple data sources into a single comprehensive report:
|
|
5
|
+
* screen name, all visible text, interactive elements, recent errors,
|
|
6
|
+
* and app state summary. Ideal for understanding the current app state
|
|
7
|
+
* in one call.
|
|
8
|
+
*
|
|
9
|
+
* Optimized: runs independent ADB calls in parallel to minimize latency.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
13
|
+
import type { ConnectionManager } from "../transport/connection-manager.js";
|
|
14
|
+
import { flattenTree } from "../util/ui-tree-parser.js";
|
|
15
|
+
import { extractTextStrings } from "../util/text-extractor.js";
|
|
16
|
+
import { parseLogcat, filterByLevel } from "../util/logcat-parser.js";
|
|
17
|
+
import { getCachedTree } from "./ui-tree.js";
|
|
18
|
+
|
|
19
|
+
export function registerDiagnoseTools(server: McpServer, cm: ConnectionManager): void {
|
|
20
|
+
server.tool(
|
|
21
|
+
"diagnose_screen",
|
|
22
|
+
"Comprehensive screen diagnosis: returns the current screen/activity name, all visible text, interactive elements (buttons, inputs), recent errors from logcat, and a summary of app state. One-stop tool for understanding what's on screen.",
|
|
23
|
+
{},
|
|
24
|
+
async () => {
|
|
25
|
+
const targetPackage = process.env.TARGET_PACKAGE ?? "com.emudebug.target";
|
|
26
|
+
const diagnosis: Record<string, unknown> = {};
|
|
27
|
+
|
|
28
|
+
// Run independent data fetches in PARALLEL for speed
|
|
29
|
+
const [activityResult, treeResult, logcatResult, processResult] = await Promise.allSettled([
|
|
30
|
+
// 1. Current activity
|
|
31
|
+
cm.adb.shell("dumpsys activity activities | grep mResumedActivity"),
|
|
32
|
+
// 2. UI tree (uses cache if fresh)
|
|
33
|
+
getCachedTree(cm, { visibleOnly: true, includeSystemUI: false }),
|
|
34
|
+
// 3. Recent logcat + PID lookup (chained)
|
|
35
|
+
(async () => {
|
|
36
|
+
const [raw, pidOutput] = await Promise.allSettled([
|
|
37
|
+
cm.adb.shell("logcat -d -v threadtime -t 500", 10_000),
|
|
38
|
+
cm.adb.shell(`pidof ${targetPackage}`),
|
|
39
|
+
]);
|
|
40
|
+
return {
|
|
41
|
+
raw: raw.status === "fulfilled" ? raw.value : "",
|
|
42
|
+
pid: pidOutput.status === "fulfilled" ? pidOutput.value.trim() : null,
|
|
43
|
+
};
|
|
44
|
+
})(),
|
|
45
|
+
// 4. Process state
|
|
46
|
+
cm.adb.shell(`ps -A | grep ${targetPackage}`),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// Process activity result
|
|
50
|
+
if (activityResult.status === "fulfilled") {
|
|
51
|
+
const match = activityResult.value.match(/u0\s+(.+?)\s+\w+/);
|
|
52
|
+
diagnosis.currentActivity = match ? match[1].trim() : activityResult.value.trim();
|
|
53
|
+
} else {
|
|
54
|
+
diagnosis.currentActivity = "unknown";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Process UI tree result
|
|
58
|
+
if (treeResult.status === "fulfilled") {
|
|
59
|
+
const tree = treeResult.value;
|
|
60
|
+
const flat = flattenTree(tree);
|
|
61
|
+
|
|
62
|
+
diagnosis.screenText = extractTextStrings(tree);
|
|
63
|
+
|
|
64
|
+
diagnosis.interactiveElements = flat
|
|
65
|
+
.filter((n) => n.clickable || n.focusable || n.scrollable)
|
|
66
|
+
.map((n) => ({
|
|
67
|
+
type: n.className.split(".").pop(),
|
|
68
|
+
text: n.text || n.contentDescription || n.resourceId || "(unnamed)",
|
|
69
|
+
resourceId: n.resourceId,
|
|
70
|
+
clickable: n.clickable,
|
|
71
|
+
enabled: n.enabled,
|
|
72
|
+
bounds: n.bounds,
|
|
73
|
+
center: n.bounds
|
|
74
|
+
? {
|
|
75
|
+
x: Math.round((n.bounds.left + n.bounds.right) / 2),
|
|
76
|
+
y: Math.round((n.bounds.top + n.bounds.bottom) / 2),
|
|
77
|
+
}
|
|
78
|
+
: null,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
diagnosis.uiSummary = {
|
|
82
|
+
totalNodes: flat.length,
|
|
83
|
+
textNodes: flat.filter((n) => n.text).length,
|
|
84
|
+
clickableNodes: flat.filter((n) => n.clickable).length,
|
|
85
|
+
inputFields: flat.filter(
|
|
86
|
+
(n) =>
|
|
87
|
+
n.className.includes("EditText") || n.className.includes("TextInput"),
|
|
88
|
+
).length,
|
|
89
|
+
scrollableContainers: flat.filter((n) => n.scrollable).length,
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
diagnosis.uiError = `Failed to get UI tree: ${treeResult.reason}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Process logcat result
|
|
96
|
+
if (logcatResult.status === "fulfilled") {
|
|
97
|
+
const { raw, pid } = logcatResult.value;
|
|
98
|
+
if (raw) {
|
|
99
|
+
const entries = parseLogcat(raw);
|
|
100
|
+
const errors = filterByLevel(entries, "E");
|
|
101
|
+
|
|
102
|
+
const appErrors = pid
|
|
103
|
+
? errors.filter((e) => e.pid === pid)
|
|
104
|
+
: errors.filter(
|
|
105
|
+
(e) =>
|
|
106
|
+
e.tag.includes("ReactNativeJS") ||
|
|
107
|
+
e.tag.includes("ReactNative") ||
|
|
108
|
+
e.tag.includes(targetPackage),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
diagnosis.recentErrors = appErrors.slice(-10).map((e) => ({
|
|
112
|
+
timestamp: e.timestamp,
|
|
113
|
+
tag: e.tag,
|
|
114
|
+
message: e.message,
|
|
115
|
+
}));
|
|
116
|
+
diagnosis.errorCount = appErrors.length;
|
|
117
|
+
} else {
|
|
118
|
+
diagnosis.recentErrors = [];
|
|
119
|
+
diagnosis.errorCount = 0;
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
diagnosis.recentErrors = [];
|
|
123
|
+
diagnosis.errorCount = 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Process state result
|
|
127
|
+
if (processResult.status === "fulfilled") {
|
|
128
|
+
diagnosis.processInfo = processResult.value.trim() || "App not running";
|
|
129
|
+
} else {
|
|
130
|
+
diagnosis.processInfo = "Unable to determine process state";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Agent status (synchronous)
|
|
134
|
+
diagnosis.agentConnected = cm.agent.isConnected();
|
|
135
|
+
if (!cm.agent.isConnected()) {
|
|
136
|
+
diagnosis.agentNote =
|
|
137
|
+
"On-device agent not connected. Some data (RN components, app state, network) " +
|
|
138
|
+
"is limited to ADB-based inspection.";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text" as const, text: JSON.stringify(diagnosis) }],
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hot Reload Tool - Trigger Metro bundler hot or full reload.
|
|
3
|
+
*
|
|
4
|
+
* Communicates with the Metro dev server to trigger a reload of the
|
|
5
|
+
* JavaScript bundle, either incremental (hot) or full.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import type { ConnectionManager } from "../transport/connection-manager.js";
|
|
11
|
+
|
|
12
|
+
/** Default Metro bundler port. */
|
|
13
|
+
const METRO_PORT = 8081;
|
|
14
|
+
|
|
15
|
+
export function registerHotReloadTools(server: McpServer, cm: ConnectionManager): void {
|
|
16
|
+
server.tool(
|
|
17
|
+
"hot_reload",
|
|
18
|
+
"Trigger a React Native reload via the Metro bundler. 'hot' performs a fast refresh (preserves state), 'full' performs a complete bundle reload. Works by sending key events or hitting the Metro dev server endpoint.",
|
|
19
|
+
{
|
|
20
|
+
type: z
|
|
21
|
+
.enum(["hot", "full"])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Reload type: 'hot' for fast refresh (default), 'full' for complete reload"),
|
|
24
|
+
},
|
|
25
|
+
async (params) => {
|
|
26
|
+
const reloadType = params.type ?? "hot";
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
if (reloadType === "full") {
|
|
30
|
+
// Method 1: Send "RR" key sequence (double-tap R in dev menu)
|
|
31
|
+
// This triggers a full reload in React Native dev mode
|
|
32
|
+
try {
|
|
33
|
+
await cm.adb.shell("input keyevent 46 46"); // R R
|
|
34
|
+
} catch {
|
|
35
|
+
// Method 2: Hit Metro /reload endpoint
|
|
36
|
+
try {
|
|
37
|
+
await cm.adb.shell(`curl -s http://localhost:${METRO_PORT}/reload`);
|
|
38
|
+
} catch {
|
|
39
|
+
// Method 3: Send the menu key then look for reload option
|
|
40
|
+
await cm.adb.shell("input keyevent 82"); // MENU key opens RN dev menu
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text" as const,
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
success: true,
|
|
50
|
+
type: "full",
|
|
51
|
+
message: "Full reload triggered. The app will reload the JS bundle from Metro.",
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
} else {
|
|
57
|
+
// Hot reload: trigger via Metro /message endpoint
|
|
58
|
+
try {
|
|
59
|
+
// Send HMR trigger through ADB reverse + Metro
|
|
60
|
+
await cm.adb.shell(
|
|
61
|
+
`curl -s -X POST http://localhost:${METRO_PORT}/message -H "Content-Type: application/json" -d '{"method":"reload"}'`,
|
|
62
|
+
);
|
|
63
|
+
} catch {
|
|
64
|
+
// Fallback: the shake gesture or menu key
|
|
65
|
+
// On emulators, Ctrl+M or MENU key opens dev menu
|
|
66
|
+
await cm.adb.shell("input keyevent 82");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text" as const,
|
|
73
|
+
text: JSON.stringify({
|
|
74
|
+
success: true,
|
|
75
|
+
type: "hot",
|
|
76
|
+
message: "Hot reload triggered. Fast Refresh will apply changes without losing state.",
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text" as const,
|
|
87
|
+
text: JSON.stringify({
|
|
88
|
+
success: false,
|
|
89
|
+
error: err instanceof Error ? err.message : String(err),
|
|
90
|
+
hints: [
|
|
91
|
+
"Ensure Metro bundler is running (npx react-native start)",
|
|
92
|
+
"Ensure ADB reverse port forwarding is set up: adb reverse tcp:8081 tcp:8081",
|
|
93
|
+
"Ensure the app is in dev mode (not a release build)",
|
|
94
|
+
],
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
isError: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Registry - Registers all EmuDebug MCP tools with the server.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { ConnectionManager } from "../transport/connection-manager.js";
|
|
7
|
+
import { registerUiTreeTools } from "./ui-tree.js";
|
|
8
|
+
import { registerAccessibilityTools } from "./accessibility.js";
|
|
9
|
+
import { registerLogcatTools } from "./logcat.js";
|
|
10
|
+
import { registerRnTools } from "./rn-tools.js";
|
|
11
|
+
import { registerAppStateTools } from "./app-state.js";
|
|
12
|
+
import { registerNetworkTools } from "./network.js";
|
|
13
|
+
import { registerInteractionTools } from "./interaction.js";
|
|
14
|
+
import { registerPerformanceTools } from "./performance.js";
|
|
15
|
+
import { registerAdbTools } from "./adb.js";
|
|
16
|
+
import { registerDiagnoseTools } from "./diagnose.js";
|
|
17
|
+
import { registerHotReloadTools } from "./hot-reload.js";
|
|
18
|
+
import { registerSmartActionTools } from "./smart-actions.js";
|
|
19
|
+
import { registerRecordingTools } from "./recording.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register all 26 EmuDebug tools with the MCP server.
|
|
23
|
+
*
|
|
24
|
+
* Tools registered:
|
|
25
|
+
* 1. get_ui_tree - UI hierarchy as JSON
|
|
26
|
+
* 2. get_screen_text - Visible text extraction
|
|
27
|
+
* 3. get_element_details - Deep element inspection
|
|
28
|
+
* 4. get_accessibility - Accessibility tree
|
|
29
|
+
* 5. get_logcat - Filtered Android logs
|
|
30
|
+
* 6. get_crash_info - JS exceptions, native crashes, ANRs
|
|
31
|
+
* 7. get_rn_component_tree - React Native component tree (Phase 2)
|
|
32
|
+
* 8. get_rn_bridge - TurboModule / bridge inspection (Phase 2)
|
|
33
|
+
* 9. get_app_state - AsyncStorage, query cache, nav, auth, etc.
|
|
34
|
+
* 10. get_network - HTTP traffic inspection
|
|
35
|
+
* 11. tap - Tap by text/resourceId/coordinates
|
|
36
|
+
* 12. type_text - Type into focused field
|
|
37
|
+
* 13. swipe - Swipe gesture
|
|
38
|
+
* 14. press_key - Android key events
|
|
39
|
+
* 15. long_press - Long press (press and hold)
|
|
40
|
+
* 16. get_performance - FPS, memory, CPU, bridge metrics
|
|
41
|
+
* 17. run_adb - Safe ADB command passthrough
|
|
42
|
+
* 18. diagnose_screen - Composite screen diagnosis (parallelized)
|
|
43
|
+
* 19. hot_reload - Metro bundler hot/full reload
|
|
44
|
+
* 20. wait_for_text - Poll until text appears on screen
|
|
45
|
+
* 21. scroll_to_text - Scroll until element is visible
|
|
46
|
+
* 22. fill_form - Batch fill multiple form fields
|
|
47
|
+
* 23. tap_and_wait - Tap element then wait for text
|
|
48
|
+
* 24. assert_screen - Quick screen assertions
|
|
49
|
+
* 25. open_deep_link - Open app via deep link URL
|
|
50
|
+
* 26. take_screenshot - Capture screen as base64 PNG
|
|
51
|
+
*/
|
|
52
|
+
export function registerAllTools(server: McpServer, cm: ConnectionManager): void {
|
|
53
|
+
registerUiTreeTools(server, cm); // 3 tools: get_ui_tree, get_screen_text, get_element_details
|
|
54
|
+
registerAccessibilityTools(server, cm); // 1 tool: get_accessibility
|
|
55
|
+
registerLogcatTools(server, cm); // 2 tools: get_logcat, get_crash_info
|
|
56
|
+
registerRnTools(server, cm); // 2 tools: get_rn_component_tree, get_rn_bridge
|
|
57
|
+
registerAppStateTools(server, cm); // 1 tool: get_app_state
|
|
58
|
+
registerNetworkTools(server, cm); // 1 tool: get_network
|
|
59
|
+
registerInteractionTools(server, cm); // 5 tools: tap, type_text, swipe, press_key, long_press
|
|
60
|
+
registerPerformanceTools(server, cm); // 1 tool: get_performance
|
|
61
|
+
registerAdbTools(server, cm); // 1 tool: run_adb
|
|
62
|
+
registerDiagnoseTools(server, cm); // 1 tool: diagnose_screen
|
|
63
|
+
registerHotReloadTools(server, cm); // 1 tool: hot_reload
|
|
64
|
+
registerSmartActionTools(server, cm); // 7 tools: wait_for_text, scroll_to_text, fill_form, tap_and_wait, assert_screen, open_deep_link, take_screenshot
|
|
65
|
+
registerRecordingTools(server, cm); // 2 tools: start_recording, stop_recording
|
|
66
|
+
}
|