@gravitylabsllc/porthole 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/LICENSE +202 -0
- package/dist/adb.js +64 -0
- package/dist/adb.js.map +1 -0
- package/dist/capture.js +165 -0
- package/dist/capture.js.map +1 -0
- package/dist/cli.js +189 -0
- package/dist/cli.js.map +1 -0
- package/dist/device.js +174 -0
- package/dist/device.js.map +1 -0
- package/dist/index.js +417 -0
- package/dist/index.js.map +1 -0
- package/dist/report.js +161 -0
- package/dist/report.js.map +1 -0
- package/dist/timeline.js +232 -0
- package/dist/timeline.js.map +1 -0
- package/dist/trace.js +276 -0
- package/dist/trace.js.map +1 -0
- package/package.json +62 -0
- package/ui/dist/assets/index--1mlZuNZ.css +1 -0
- package/ui/dist/assets/index-BeVGHRFm.js +68 -0
- package/ui/dist/index.html +23 -0
package/dist/device.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Copyright 2026 Gravity Labs
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import net from "node:net";
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
5
|
+
const RECONNECT_MIN_MS = 500;
|
|
6
|
+
const RECONNECT_MAX_MS = 5_000;
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 5_000;
|
|
8
|
+
/**
|
|
9
|
+
* Talks to the app over the adb-forwarded loopback port.
|
|
10
|
+
*
|
|
11
|
+
* Reconnects on its own, because the far end is an app being actively developed:
|
|
12
|
+
* it gets killed, reinstalled and relaunched constantly, and none of that should
|
|
13
|
+
* require restarting the MCP server.
|
|
14
|
+
*/
|
|
15
|
+
export class DeviceClient extends EventEmitter {
|
|
16
|
+
host;
|
|
17
|
+
port;
|
|
18
|
+
socket = null;
|
|
19
|
+
buffer = "";
|
|
20
|
+
nextId = 1;
|
|
21
|
+
pending = new Map();
|
|
22
|
+
reconnectDelay = RECONNECT_MIN_MS;
|
|
23
|
+
reconnectTimer = null;
|
|
24
|
+
closed = false;
|
|
25
|
+
state = "disconnected";
|
|
26
|
+
hello = null;
|
|
27
|
+
lastError = null;
|
|
28
|
+
constructor(host, port) {
|
|
29
|
+
super();
|
|
30
|
+
this.host = host;
|
|
31
|
+
this.port = port;
|
|
32
|
+
}
|
|
33
|
+
start() {
|
|
34
|
+
this.closed = false;
|
|
35
|
+
this.connect();
|
|
36
|
+
}
|
|
37
|
+
stop() {
|
|
38
|
+
this.closed = true;
|
|
39
|
+
if (this.reconnectTimer)
|
|
40
|
+
clearTimeout(this.reconnectTimer);
|
|
41
|
+
this.socket?.destroy();
|
|
42
|
+
this.socket = null;
|
|
43
|
+
this.setState("disconnected");
|
|
44
|
+
}
|
|
45
|
+
connect() {
|
|
46
|
+
if (this.closed || this.socket)
|
|
47
|
+
return;
|
|
48
|
+
this.setState("connecting");
|
|
49
|
+
const socket = net.createConnection({ host: this.host, port: this.port });
|
|
50
|
+
socket.setNoDelay(true);
|
|
51
|
+
socket.setEncoding("utf8");
|
|
52
|
+
this.socket = socket;
|
|
53
|
+
socket.on("connect", () => {
|
|
54
|
+
this.reconnectDelay = RECONNECT_MIN_MS;
|
|
55
|
+
this.lastError = null;
|
|
56
|
+
this.setState("connected");
|
|
57
|
+
// hello doubles as a liveness check and as the timeline's origin.
|
|
58
|
+
this.request("hello")
|
|
59
|
+
.then((hello) => {
|
|
60
|
+
this.hello = hello;
|
|
61
|
+
this.emit("hello", hello);
|
|
62
|
+
})
|
|
63
|
+
.catch((error) => {
|
|
64
|
+
this.lastError = error.message;
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
socket.on("data", (chunk) => this.onData(chunk));
|
|
68
|
+
socket.on("error", (error) => {
|
|
69
|
+
this.lastError = error.message;
|
|
70
|
+
});
|
|
71
|
+
socket.on("close", () => {
|
|
72
|
+
this.socket = null;
|
|
73
|
+
this.hello = null;
|
|
74
|
+
this.failPending("device disconnected");
|
|
75
|
+
this.setState("disconnected");
|
|
76
|
+
this.scheduleReconnect();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
scheduleReconnect() {
|
|
80
|
+
if (this.closed || this.reconnectTimer)
|
|
81
|
+
return;
|
|
82
|
+
this.reconnectTimer = setTimeout(() => {
|
|
83
|
+
this.reconnectTimer = null;
|
|
84
|
+
this.connect();
|
|
85
|
+
}, this.reconnectDelay);
|
|
86
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
87
|
+
}
|
|
88
|
+
onData(chunk) {
|
|
89
|
+
this.buffer += chunk;
|
|
90
|
+
let newline = this.buffer.indexOf("\n");
|
|
91
|
+
while (newline >= 0) {
|
|
92
|
+
const line = this.buffer.slice(0, newline).trim();
|
|
93
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
94
|
+
if (line.length > 0)
|
|
95
|
+
this.onLine(line);
|
|
96
|
+
newline = this.buffer.indexOf("\n");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
onLine(line) {
|
|
100
|
+
let frame;
|
|
101
|
+
try {
|
|
102
|
+
frame = JSON.parse(line);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if ("event" in frame) {
|
|
108
|
+
this.emit("event", frame);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const waiter = this.pending.get(frame.id);
|
|
112
|
+
if (!waiter)
|
|
113
|
+
return;
|
|
114
|
+
this.pending.delete(frame.id);
|
|
115
|
+
clearTimeout(waiter.timer);
|
|
116
|
+
if (frame.ok)
|
|
117
|
+
waiter.resolve(frame.result);
|
|
118
|
+
else
|
|
119
|
+
waiter.reject(new Error(frame.error ?? "unknown device error"));
|
|
120
|
+
}
|
|
121
|
+
failPending(reason) {
|
|
122
|
+
for (const [, waiter] of this.pending) {
|
|
123
|
+
clearTimeout(waiter.timer);
|
|
124
|
+
waiter.reject(new Error(reason));
|
|
125
|
+
}
|
|
126
|
+
this.pending.clear();
|
|
127
|
+
}
|
|
128
|
+
setState(state) {
|
|
129
|
+
if (this.state === state)
|
|
130
|
+
return;
|
|
131
|
+
this.state = state;
|
|
132
|
+
this.emit("state", state);
|
|
133
|
+
}
|
|
134
|
+
request(method, params = {}) {
|
|
135
|
+
const socket = this.socket;
|
|
136
|
+
if (!socket || this.state !== "connected") {
|
|
137
|
+
return Promise.reject(new Error(this.notConnectedMessage()));
|
|
138
|
+
}
|
|
139
|
+
const id = this.nextId++;
|
|
140
|
+
// Strip undefined so optional tool arguments do not become JSON nulls that
|
|
141
|
+
// the Kotlin side would have to special-case.
|
|
142
|
+
const cleaned = {};
|
|
143
|
+
for (const [key, value] of Object.entries(params)) {
|
|
144
|
+
if (value !== undefined)
|
|
145
|
+
cleaned[key] = value;
|
|
146
|
+
}
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const timer = setTimeout(() => {
|
|
149
|
+
this.pending.delete(id);
|
|
150
|
+
reject(new Error(`device did not answer '${method}' within ${REQUEST_TIMEOUT_MS}ms`));
|
|
151
|
+
}, REQUEST_TIMEOUT_MS);
|
|
152
|
+
this.pending.set(id, {
|
|
153
|
+
resolve: resolve,
|
|
154
|
+
reject,
|
|
155
|
+
timer,
|
|
156
|
+
});
|
|
157
|
+
socket.write(JSON.stringify({ id, method, params: cleaned }) + "\n");
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
notConnectedMessage() {
|
|
161
|
+
return [
|
|
162
|
+
`Not connected to the app on ${this.host}:${this.port}.`,
|
|
163
|
+
this.lastError ? `Last socket error: ${this.lastError}.` : null,
|
|
164
|
+
"Check, in order:",
|
|
165
|
+
" 1. the debug build is running on the device (the porthole starts with the process)",
|
|
166
|
+
" 2. the adb bridge is up: 'adb forward tcp:PORT tcp:PORT', which",
|
|
167
|
+
" 'porthole ui' and './gradlew portholeConnect' both do for you",
|
|
168
|
+
` 3. nothing else on this machine is holding ${this.port}`,
|
|
169
|
+
]
|
|
170
|
+
.filter(Boolean)
|
|
171
|
+
.join("\n");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../src/device.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright 2026 Gravity Labs
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { DeviceClient } from "./device.js";
|
|
8
|
+
import { TimelineServer } from "./timeline.js";
|
|
9
|
+
const HOST = process.env.PORTHOLE_HOST ?? "127.0.0.1";
|
|
10
|
+
const PORT = Number(process.env.PORTHOLE_PORT ?? 8677);
|
|
11
|
+
const UI_PORT = Number(process.env.PORTHOLE_UI_PORT ?? 8678);
|
|
12
|
+
const device = new DeviceClient(HOST, PORT);
|
|
13
|
+
const timeline = new TimelineServer(device, UI_PORT);
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: "porthole",
|
|
16
|
+
version: "0.1.0",
|
|
17
|
+
});
|
|
18
|
+
/** Summary line first, then the JSON. The summary is often the whole answer. */
|
|
19
|
+
function ok(summary, payload) {
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: "text", text: `${summary}\n\n${JSON.stringify(payload, null, 2)}` }],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function fail(error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
async function call(method, params, summarise) {
|
|
29
|
+
try {
|
|
30
|
+
const result = await device.request(method, params);
|
|
31
|
+
return ok(summarise(result), result);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return fail(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// tools
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
server.registerTool("porthole_status", {
|
|
41
|
+
title: "Porthole status",
|
|
42
|
+
description: "Whether the porthole is connected to a running app, which collectors are active, and what to " +
|
|
43
|
+
"do if it is not. Start here when another tool reports it cannot reach the device.",
|
|
44
|
+
inputSchema: {},
|
|
45
|
+
annotations: { readOnlyHint: true },
|
|
46
|
+
}, async () => {
|
|
47
|
+
const payload = {
|
|
48
|
+
state: device.state,
|
|
49
|
+
host: HOST,
|
|
50
|
+
port: PORT,
|
|
51
|
+
app: device.hello,
|
|
52
|
+
timelineUi: timeline.isRunning() ? timeline.url() : null,
|
|
53
|
+
bufferedEvents: timeline.buffer().length,
|
|
54
|
+
lastError: device.lastError,
|
|
55
|
+
};
|
|
56
|
+
const summary = device.state === "connected" && device.hello
|
|
57
|
+
? `Connected to ${device.hello.packageName} on ${device.hello.device} ` +
|
|
58
|
+
`(API ${device.hello.sdkInt}). Collectors: ${device.hello.collectors.join(", ")}.`
|
|
59
|
+
: device.notConnectedMessage();
|
|
60
|
+
return ok(summary, payload);
|
|
61
|
+
});
|
|
62
|
+
server.registerTool("recompositions", {
|
|
63
|
+
title: "Recomposition counts",
|
|
64
|
+
description: "How many times each instrumented composable recomposed, and which state keys were written " +
|
|
65
|
+
"just before each recomposition. Use it to find the composable doing needless work and the " +
|
|
66
|
+
"state that keeps invalidating it.\n\n" +
|
|
67
|
+
"Two limits worth holding in mind: only call sites wrapped in PortholeScreen or " +
|
|
68
|
+
"Modifier.portholeNode are counted, so an absent composable is uninstrumented rather than " +
|
|
69
|
+
"idle; and triggeredBy is a temporal correlation within a ~32ms window, not a causal read " +
|
|
70
|
+
"of the invalidation graph, so several states changing in one frame all get listed.\n\n" +
|
|
71
|
+
"Keys like 'unnamed#3f2a1c' are state objects nobody named. In a Compose app most of them " +
|
|
72
|
+
"belong to the framework — ripples, scroll offsets, focus, animation clocks — and are not " +
|
|
73
|
+
"worth chasing. A key that is yours and still unnamed means its owner was never registered: " +
|
|
74
|
+
"Porthole.registerViewModel for a ViewModel, collectAsNamedState for a Flow, " +
|
|
75
|
+
"rememberNamedState for state a composable creates for itself.\n\n" +
|
|
76
|
+
"A key carrying 'holds' is anonymous state that was found holding one of the app's own " +
|
|
77
|
+
"types, so it is definitely the app's and definitely unregistered — that one is worth " +
|
|
78
|
+
"chasing. Its absence proves nothing: an unregistered Int is indistinguishable from a " +
|
|
79
|
+
"ripple, so most of the app's own unnamed state will not be flagged.",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
screen: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe("Only nodes on this screen, matched against the enclosing PortholeScreen name."),
|
|
85
|
+
sinceMs: z
|
|
86
|
+
.number()
|
|
87
|
+
.int()
|
|
88
|
+
.positive()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Look back this many milliseconds. Omit for everything still buffered."),
|
|
91
|
+
from: z
|
|
92
|
+
.number()
|
|
93
|
+
.int()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("Absolute start, in the device uptime clock every event carries. Use this to ask " +
|
|
96
|
+
"about a moment seen on the timeline instead of guessing a lookback."),
|
|
97
|
+
to: z.number().int().optional().describe("Absolute end, same clock. Defaults to now."),
|
|
98
|
+
},
|
|
99
|
+
annotations: { readOnlyHint: true },
|
|
100
|
+
}, async ({ screen, sinceMs, from, to }) => call("recompositions", { screen, sinceMs, from, to }, (report) => {
|
|
101
|
+
if (report.nodes.length === 0) {
|
|
102
|
+
return "No instrumented composable recomposed in that window.";
|
|
103
|
+
}
|
|
104
|
+
const top = report.nodes[0];
|
|
105
|
+
const cause = top.triggeredBy[0];
|
|
106
|
+
const total = report.nodes.reduce((sum, node) => sum + node.count, 0);
|
|
107
|
+
return (`${total} recompositions across ${report.nodes.length} nodes. ` +
|
|
108
|
+
`Worst: ${top.name} at ${top.count}` +
|
|
109
|
+
(cause ? `, most often after a write to ${cause.key} (${cause.count} of them).` : "."));
|
|
110
|
+
}));
|
|
111
|
+
server.registerTool("semantics_tree", {
|
|
112
|
+
title: "Semantics tree",
|
|
113
|
+
description: "The Compose semantics tree with a stable id per node. stableId is a structural path hash: " +
|
|
114
|
+
"the same UI produces the same id across captures and across process restarts, so two " +
|
|
115
|
+
"captures can be diffed. Nodes carrying a porthole node id line up with the ids in the " +
|
|
116
|
+
"recompositions report.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
merged: z
|
|
119
|
+
.boolean()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("Merged tree (what accessibility services see). Default true."),
|
|
122
|
+
maxDepth: z.number().int().positive().optional().describe("Depth cap. Default 40."),
|
|
123
|
+
maxNodes: z.number().int().positive().optional().describe("Node budget. Default 1500."),
|
|
124
|
+
},
|
|
125
|
+
annotations: { readOnlyHint: true },
|
|
126
|
+
}, async ({ merged, maxDepth, maxNodes }) => call("semantics_tree", { merged, maxDepth, maxNodes }, (tree) => tree.error ? tree.error : tree.root ? "Captured the semantics tree." : "Empty tree."));
|
|
127
|
+
server.registerTool("nav_state", {
|
|
128
|
+
title: "Navigation state",
|
|
129
|
+
description: "The current back stack with each entry's route, arguments and lifecycle state, plus the " +
|
|
130
|
+
"deep link that opened the app if there was one. Answers 'how did I get to this screen' " +
|
|
131
|
+
"and 'what arguments is it actually holding', which is usually where the bug is.",
|
|
132
|
+
inputSchema: {},
|
|
133
|
+
annotations: { readOnlyHint: true },
|
|
134
|
+
}, async () => call("nav_state", {}, (nav) => nav.error ??
|
|
135
|
+
`At ${nav.current?.route ?? "an unnamed destination"} with ${nav.backStack.length} entries on the stack.`));
|
|
136
|
+
server.registerTool("state", {
|
|
137
|
+
title: "ViewModel state",
|
|
138
|
+
description: "Current values of the state held by registered ViewModels. Each field says whether writes " +
|
|
139
|
+
"to it are attributable — meaning snapshot state the recomposition report can name. A " +
|
|
140
|
+
"StateFlow is never attributable on its own; collectAsNamedState is what makes the State " +
|
|
141
|
+
"it produces nameable.",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
viewModel: z
|
|
144
|
+
.string()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("Registered name or class name. Omit for every registered owner."),
|
|
147
|
+
},
|
|
148
|
+
annotations: { readOnlyHint: true },
|
|
149
|
+
}, async ({ viewModel }) => call("state", { viewModel }, (dump) => {
|
|
150
|
+
if (dump.owners.length === 0) {
|
|
151
|
+
return 'No ViewModels registered. Call Porthole.registerViewModel("CartViewModel", vm) where you obtain it.';
|
|
152
|
+
}
|
|
153
|
+
return dump.owners.map((owner) => `${owner.name} (${owner.fields.length} fields)`).join(", ");
|
|
154
|
+
}));
|
|
155
|
+
server.registerTool("inflight", {
|
|
156
|
+
title: "In-flight work",
|
|
157
|
+
description: "Open HTTP calls with the phase each is stuck in, database queries currently executing and " +
|
|
158
|
+
"the thread running them, and enqueued or running WorkManager jobs. This is the tool for " +
|
|
159
|
+
"'why is this screen still spinning'.\n\n" +
|
|
160
|
+
"Also returns recentHttp: the last 25 finished calls with status, headers and — when the " +
|
|
161
|
+
"app opted in via BodyCapture — request and response body previews. A body with text:null " +
|
|
162
|
+
"carries an omittedReason saying why it was not captured (disabled, wrong content type, " +
|
|
163
|
+
"one-shot stream); that is different from the call having had no body at all.",
|
|
164
|
+
inputSchema: {},
|
|
165
|
+
annotations: { readOnlyHint: true },
|
|
166
|
+
}, async () => call("inflight", {}, (flight) => {
|
|
167
|
+
const parts = [];
|
|
168
|
+
if (flight.http.length) {
|
|
169
|
+
const worst = flight.http[0];
|
|
170
|
+
parts.push(`${flight.http.length} HTTP call(s), oldest ${worst.method} ${worst.url} ` +
|
|
171
|
+
`in '${worst.phase}' for ${worst.elapsedMs}ms`);
|
|
172
|
+
}
|
|
173
|
+
if (flight.queries.length) {
|
|
174
|
+
const writes = flight.queries.filter((q) => q.kind === "write").length;
|
|
175
|
+
parts.push(`${flight.queries.length} query(ies) running on ${flight.queries[0].thread}` +
|
|
176
|
+
(writes ? ` (${writes} write)` : ""));
|
|
177
|
+
}
|
|
178
|
+
if (flight.work.length)
|
|
179
|
+
parts.push(`${flight.work.length} work job(s)`);
|
|
180
|
+
const recent = flight.recentHttp ?? [];
|
|
181
|
+
const failed = recent.filter((c) => c.status !== null && c.status >= 400);
|
|
182
|
+
if (recent.length) {
|
|
183
|
+
parts.push(`${recent.length} recent call(s)` +
|
|
184
|
+
(failed.length ? `, ${failed.length} with a ${failed[0].status}` : ""));
|
|
185
|
+
}
|
|
186
|
+
return parts.length ? parts.join("; ") : "Nothing in flight.";
|
|
187
|
+
}));
|
|
188
|
+
server.registerTool("frames", {
|
|
189
|
+
title: "Frame timing",
|
|
190
|
+
description: "How many frames the app dropped, and where the time went in the worst ones. This is the " +
|
|
191
|
+
"outcome every other collector is a proxy for: a recomposition count only matters because " +
|
|
192
|
+
"of what it does to frame time.\n\n" +
|
|
193
|
+
"worstPhase names the stage that dominated a janky frame, which is what decides where to " +
|
|
194
|
+
"look: layoutMeasure or draw points at composition doing too much, gpu or swapBuffers at " +
|
|
195
|
+
"overdraw or an expensive shader, unknownDelay at the main thread being busy with " +
|
|
196
|
+
"something that is not drawing at all. Pair a jank cluster with recompositions over the " +
|
|
197
|
+
"same from/to window to see whether recomposition is the cause.\n\n" +
|
|
198
|
+
"Frames with firstDraw are a window being drawn for the first time and are expected to be " +
|
|
199
|
+
"slow. Needs API 24 or newer.",
|
|
200
|
+
inputSchema: {
|
|
201
|
+
sinceMs: z.number().int().positive().optional().describe("Only the last N milliseconds."),
|
|
202
|
+
from: z.number().int().optional().describe("Absolute start, device uptime clock."),
|
|
203
|
+
to: z.number().int().optional().describe("Absolute end, same clock."),
|
|
204
|
+
limit: z
|
|
205
|
+
.number()
|
|
206
|
+
.int()
|
|
207
|
+
.positive()
|
|
208
|
+
.max(200)
|
|
209
|
+
.optional()
|
|
210
|
+
.describe("Worst N frames. Default 20."),
|
|
211
|
+
},
|
|
212
|
+
annotations: { readOnlyHint: true },
|
|
213
|
+
}, async ({ sinceMs, from, to, limit }) => call("frames", { sinceMs, from, to, limit }, (report) => {
|
|
214
|
+
if (report.totalFrames === 0)
|
|
215
|
+
return "No frames observed yet.";
|
|
216
|
+
const rate = ((report.jankyFrames / report.totalFrames) * 100).toFixed(1);
|
|
217
|
+
const worst = report.worst[0];
|
|
218
|
+
const byPhase = {};
|
|
219
|
+
for (const frame of report.worst) {
|
|
220
|
+
byPhase[frame.worstPhase] = (byPhase[frame.worstPhase] ?? 0) + 1;
|
|
221
|
+
}
|
|
222
|
+
const phases = Object.entries(byPhase)
|
|
223
|
+
.sort((a, b) => b[1] - a[1])
|
|
224
|
+
.map(([phase, n]) => `${phase} ${n}`)
|
|
225
|
+
.join(", ");
|
|
226
|
+
return (`${report.jankyFrames} of ${report.totalFrames} frames janky (${rate}%), ` +
|
|
227
|
+
`budget ${report.frameIntervalMs}ms.` +
|
|
228
|
+
(worst
|
|
229
|
+
? ` Worst ${worst.totalMs}ms, ${worst.missedFrames} refresh(es) missed, mostly ` +
|
|
230
|
+
`${worst.worstPhase}. Across the worst frames: ${phases}.`
|
|
231
|
+
: ""));
|
|
232
|
+
}));
|
|
233
|
+
server.registerTool("blocking", {
|
|
234
|
+
title: "Main thread blocking",
|
|
235
|
+
description: "What held the main thread: stalls longer than the threshold, with the stack the main " +
|
|
236
|
+
"thread was in at the time, and any database query that ran on it.\n\n" +
|
|
237
|
+
"Stalls are found by pinging the main looper and timing the reply, so the duration is how " +
|
|
238
|
+
"long everything queued ahead of the ping took. The stack is sampled once, when the ping " +
|
|
239
|
+
"goes overdue, and app frames are listed first because the top frame is usually a native " +
|
|
240
|
+
"read and the line you can change is a few frames down.\n\n" +
|
|
241
|
+
"Database work on the main thread is reported however fast it was: a 4ms disk read in the " +
|
|
242
|
+
"frame loop is a defect that has not bitten yet. For hitches shorter than the threshold, " +
|
|
243
|
+
"use `frames` instead — that measures every frame, this one catches the big stops.",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
sinceMs: z.number().int().positive().optional().describe("Only the last N milliseconds."),
|
|
246
|
+
from: z.number().int().optional().describe("Absolute start, device uptime clock."),
|
|
247
|
+
to: z.number().int().optional().describe("Absolute end, same clock."),
|
|
248
|
+
limit: z
|
|
249
|
+
.number()
|
|
250
|
+
.int()
|
|
251
|
+
.positive()
|
|
252
|
+
.max(100)
|
|
253
|
+
.optional()
|
|
254
|
+
.describe("Worst N of each. Default 20."),
|
|
255
|
+
},
|
|
256
|
+
annotations: { readOnlyHint: true },
|
|
257
|
+
}, async ({ sinceMs, from, to, limit }) => call("blocking", { sinceMs, from, to, limit }, (report) => {
|
|
258
|
+
const parts = [];
|
|
259
|
+
if (report.stalls.length) {
|
|
260
|
+
const worst = report.stalls[0];
|
|
261
|
+
parts.push(`${report.stalls.length} stall(s) over ${report.stallThresholdMs}ms, worst ` +
|
|
262
|
+
`${worst.durationMs}ms in ${worst.stack.split("\n")[0]}`);
|
|
263
|
+
}
|
|
264
|
+
if (report.mainThreadQueries.length) {
|
|
265
|
+
const worst = report.mainThreadQueries[0];
|
|
266
|
+
parts.push(`${report.mainThreadQueries.length} database ${report.mainThreadQueries.length === 1 ? "query" : "queries"} ` +
|
|
267
|
+
`on the main thread, worst ${worst.elapsedMs}ms: ${worst.sql.slice(0, 80)}`);
|
|
268
|
+
}
|
|
269
|
+
return parts.length ? parts.join(". ") : "Nothing blocked the main thread in this window.";
|
|
270
|
+
}));
|
|
271
|
+
server.registerTool("logs", {
|
|
272
|
+
title: "App logs",
|
|
273
|
+
description: "The app's own logcat output, captured in-process and streamed over the same socket as " +
|
|
274
|
+
"everything else — no adb needed. Stack traces arrive attached to the line that started " +
|
|
275
|
+
"them rather than as loose fragments.\n\n" +
|
|
276
|
+
"Entries carry the same uptime clock as the timeline, so a log line can be placed against " +
|
|
277
|
+
"a recomposition burst or an HTTP call. Only the app's own output is visible, and the " +
|
|
278
|
+
"porthole's own tag is excluded.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
level: z
|
|
281
|
+
.enum(["V", "D", "I", "W", "E", "F"])
|
|
282
|
+
.optional()
|
|
283
|
+
.describe("Minimum level. 'W' for warnings and worse, which is usually what you want."),
|
|
284
|
+
tag: z.string().optional().describe("Substring match on the tag."),
|
|
285
|
+
contains: z.string().optional().describe("Substring match on the message."),
|
|
286
|
+
sinceMs: z.number().int().positive().optional().describe("Only the last N milliseconds."),
|
|
287
|
+
from: z
|
|
288
|
+
.number()
|
|
289
|
+
.int()
|
|
290
|
+
.optional()
|
|
291
|
+
.describe("Absolute start, in the device uptime clock every event carries. Use this to ask " +
|
|
292
|
+
"about a moment seen on the timeline instead of guessing a lookback."),
|
|
293
|
+
to: z.number().int().optional().describe("Absolute end, same clock. Defaults to now."),
|
|
294
|
+
limit: z
|
|
295
|
+
.number()
|
|
296
|
+
.int()
|
|
297
|
+
.positive()
|
|
298
|
+
.max(2000)
|
|
299
|
+
.optional()
|
|
300
|
+
.describe("Newest N entries. Default 200."),
|
|
301
|
+
},
|
|
302
|
+
annotations: { readOnlyHint: true },
|
|
303
|
+
}, async ({ level, tag, contains, sinceMs, from, to, limit }) => call("logs", { level, tag, contains, sinceMs, from, to, limit }, (page) => {
|
|
304
|
+
if (!page.capturing) {
|
|
305
|
+
return page.notes.join(" ") || "Log capture is not running.";
|
|
306
|
+
}
|
|
307
|
+
if (page.entries.length === 0) {
|
|
308
|
+
return page.notes.join(" ") || "No log entries matched.";
|
|
309
|
+
}
|
|
310
|
+
const counts = {};
|
|
311
|
+
for (const entry of page.entries)
|
|
312
|
+
counts[entry.level] = (counts[entry.level] ?? 0) + 1;
|
|
313
|
+
const worst = page.entries
|
|
314
|
+
.filter((entry) => entry.level === "E" || entry.level === "F")
|
|
315
|
+
.at(-1);
|
|
316
|
+
return (`${page.entries.length} entries (` +
|
|
317
|
+
Object.entries(counts)
|
|
318
|
+
.map(([level, count]) => `${level} ${count}`)
|
|
319
|
+
.join(", ") +
|
|
320
|
+
")" +
|
|
321
|
+
(worst
|
|
322
|
+
? `. Latest error: ${worst.tag}: ${worst.message.split("\n")[0].slice(0, 120)}`
|
|
323
|
+
: "."));
|
|
324
|
+
}));
|
|
325
|
+
server.registerTool("timeline", {
|
|
326
|
+
title: "Event timeline",
|
|
327
|
+
description: "Raw event stream: recompositions, state writes, navigation, HTTP and database start/end. " +
|
|
328
|
+
"Use it to order events relative to each other — which write came before which navigation, " +
|
|
329
|
+
"what the app was doing while a call was open.",
|
|
330
|
+
inputSchema: {
|
|
331
|
+
sinceMs: z
|
|
332
|
+
.number()
|
|
333
|
+
.int()
|
|
334
|
+
.positive()
|
|
335
|
+
.optional()
|
|
336
|
+
.describe("Only events from the last N milliseconds of device uptime."),
|
|
337
|
+
kinds: z
|
|
338
|
+
.array(z.string())
|
|
339
|
+
.optional()
|
|
340
|
+
.describe("Filter by event name: recompose, state_write, frame, nav, http_start, http_end, " +
|
|
341
|
+
"db_start, db_end, log."),
|
|
342
|
+
limit: z
|
|
343
|
+
.number()
|
|
344
|
+
.int()
|
|
345
|
+
.positive()
|
|
346
|
+
.max(5000)
|
|
347
|
+
.optional()
|
|
348
|
+
.describe("Newest N events. Default 500."),
|
|
349
|
+
},
|
|
350
|
+
annotations: { readOnlyHint: true },
|
|
351
|
+
}, async ({ sinceMs, kinds, limit }) => {
|
|
352
|
+
try {
|
|
353
|
+
// Prefer the local buffer: it holds more history than the device ring and
|
|
354
|
+
// survives the app being restarted underneath us.
|
|
355
|
+
let events = timeline.buffer();
|
|
356
|
+
if (events.length === 0) {
|
|
357
|
+
const page = await device.request("timeline", {
|
|
358
|
+
limit: limit ?? 500,
|
|
359
|
+
});
|
|
360
|
+
events = page.events;
|
|
361
|
+
}
|
|
362
|
+
if (sinceMs !== undefined && events.length > 0) {
|
|
363
|
+
const newest = events[events.length - 1].t;
|
|
364
|
+
events = events.filter((event) => event.t >= newest - sinceMs);
|
|
365
|
+
}
|
|
366
|
+
if (kinds?.length) {
|
|
367
|
+
const wanted = new Set(kinds);
|
|
368
|
+
events = events.filter((event) => wanted.has(event.event));
|
|
369
|
+
}
|
|
370
|
+
events = events.slice(-(limit ?? 500));
|
|
371
|
+
const counts = {};
|
|
372
|
+
for (const event of events)
|
|
373
|
+
counts[event.event] = (counts[event.event] ?? 0) + 1;
|
|
374
|
+
const span = events.length > 1 ? events[events.length - 1].t - events[0].t : 0;
|
|
375
|
+
const summary = events.length === 0
|
|
376
|
+
? "No events buffered yet. Interact with the app and try again."
|
|
377
|
+
: `${events.length} events over ${span}ms: ` +
|
|
378
|
+
Object.entries(counts)
|
|
379
|
+
.map(([kind, count]) => `${kind} ${count}`)
|
|
380
|
+
.join(", ");
|
|
381
|
+
return ok(summary, { events });
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
return fail(error);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
server.registerTool("open_timeline", {
|
|
388
|
+
title: "Open the timeline UI",
|
|
389
|
+
description: "Starts the local timeline UI and returns its URL. Lanes for recompositions, state writes, " +
|
|
390
|
+
"navigation, network and database, on a shared time axis. Open it in a browser; it updates " +
|
|
391
|
+
"live over a WebSocket.",
|
|
392
|
+
inputSchema: {},
|
|
393
|
+
}, async () => {
|
|
394
|
+
try {
|
|
395
|
+
const url = await timeline.start();
|
|
396
|
+
return ok(`Timeline UI running at ${url}`, { url, events: timeline.buffer().length });
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
return fail(error);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// boot
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
device.start();
|
|
406
|
+
// stdout belongs to the MCP transport; anything we say goes to stderr.
|
|
407
|
+
device.on("state", (state) => process.stderr.write(`[porthole] device ${state}\n`));
|
|
408
|
+
const shutdown = () => {
|
|
409
|
+
device.stop();
|
|
410
|
+
timeline.stop();
|
|
411
|
+
process.exit(0);
|
|
412
|
+
};
|
|
413
|
+
process.on("SIGINT", shutdown);
|
|
414
|
+
process.on("SIGTERM", shutdown);
|
|
415
|
+
await server.connect(new StdioServerTransport());
|
|
416
|
+
process.stderr.write(`[porthole] MCP server ready, device target ${HOST}:${PORT}\n`);
|
|
417
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|