roku-mcp 1.4.0 → 1.5.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/README.md CHANGED
@@ -209,6 +209,21 @@ Analyze `.bsprof` files generated by Roku devices. These tools use [bsprof-cli](
209
209
 
210
210
  To generate a `.bsprof` file, enable the profiler in your Roku app's `manifest` (`bs_prof_enabled=true`), run the app, and download the profile from `http://<device-ip>:8080`.
211
211
 
212
+ ### Perfetto Tracing
213
+
214
+ Record, analyze, and compare Perfetto traces from Roku devices. Requires **Roku OS 15.1+**. These tools use [roku-perfetto](https://www.npmjs.com/package/roku-perfetto) for ECP control, WebSocket recording, and PerfettoSQL analysis.
215
+
216
+ | Tool | Description |
217
+ |---|---|
218
+ | `roku_perfetto_enable` | Enable Perfetto tracing for a channel via ECP (tracing starts on next app launch) |
219
+ | `roku_perfetto_start` | Start recording a Perfetto trace via WebSocket binary stream |
220
+ | `roku_perfetto_stop` | Stop recording and return file path, size, and duration |
221
+ | `analyze_perfetto` | Analyze a .trace file — summary, frame-drops, key-events, observers, rendezvous, set-fields, or threads. Returns AI-friendly structured JSON with suggestions. |
222
+ | `compare_perfetto` | Compare two .trace files to detect performance regressions and improvements |
223
+ | `query_perfetto` | Run a raw PerfettoSQL query against a trace file for custom analysis |
224
+
225
+ Workflow: enable tracing → start recording → interact with app → stop recording → analyze. The `.trace` files can also be opened at [ui.perfetto.dev](https://ui.perfetto.dev/).
226
+
212
227
  ## Requirements
213
228
 
214
229
  - Node.js 18+
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AAQvB,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAAQ,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAalF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AASvB,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAAQ,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAclF"}
package/dist/index.js CHANGED
@@ -6,16 +6,18 @@ import { registerEcpTools } from './tools/ecp.js';
6
6
  import { registerScreenshotTools } from './tools/screenshot.js';
7
7
  import { registerConsoleTools } from './tools/console.js';
8
8
  import { registerBsprofTools } from './tools/bsprof.js';
9
+ import { registerPerfettoTools } from './tools/perfetto.js';
9
10
  export default function createServer(_options) {
10
11
  const server = new McpServer({
11
12
  name: 'roku-mcp',
12
- version: '1.4.0',
13
+ version: '1.5.0',
13
14
  });
14
15
  registerDeployTools(server);
15
16
  registerEcpTools(server);
16
17
  registerScreenshotTools(server);
17
18
  registerConsoleTools(server);
18
19
  registerBsprofTools(server);
20
+ registerPerfettoTools(server);
19
21
  return server.server;
20
22
  }
21
23
  const isDirectRun = typeof process !== 'undefined' &&
@@ -26,13 +28,14 @@ if (isDirectRun) {
26
28
  const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
27
29
  const server = new McpServer({
28
30
  name: 'roku-mcp',
29
- version: '1.4.0',
31
+ version: '1.5.0',
30
32
  });
31
33
  registerDeployTools(server);
32
34
  registerEcpTools(server);
33
35
  registerScreenshotTools(server);
34
36
  registerConsoleTools(server);
35
37
  registerBsprofTools(server);
38
+ registerPerfettoTools(server);
36
39
  const transport = new StdioServerTransport();
37
40
  await server.connect(transport);
38
41
  })();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAA8C;IACjF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAE5B,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED,MAAM,WAAW,GACf,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;AAEnF,IAAI,WAAW,EAAE,CAAC;IAChB,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;QAC3F,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;YAC3B,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAE5B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAA8C;IACjF,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAE9B,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED,MAAM,WAAW,GACf,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;AAEnF,IAAI,WAAW,EAAE,CAAC;IAChB,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,EAAE,oBAAoB,EAAE,GAAG,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;QAC3F,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;YAC3B,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QAEH,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACzB,uBAAuB,CAAC,MAAM,CAAC,CAAC;QAChC,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAE9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC,CAAC,EAAE,CAAC;AACP,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerPerfettoTools(server: McpServer): void;
3
+ //# sourceMappingURL=perfetto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perfetto.d.ts","sourceRoot":"","sources":["../../src/tools/perfetto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAepE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkP7D"}
@@ -0,0 +1,222 @@
1
+ import { z } from 'zod';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { PerfettoClient, comparePerfetto, openTrace } from 'roku-perfetto';
5
+ import { resolveHost, friendlyError } from '../roku-config.js';
6
+ function jsonify(value) {
7
+ return JSON.stringify(value, (_k, v) => (typeof v === 'bigint' ? Number(v) : v), 2);
8
+ }
9
+ let client = null;
10
+ let clientHost = null;
11
+ export function registerPerfettoTools(server) {
12
+ // ---------------------------------------------------------------------------
13
+ // roku_perfetto_enable
14
+ // ---------------------------------------------------------------------------
15
+ server.registerTool('roku_perfetto_enable', {
16
+ description: 'Enable Perfetto tracing for a Roku channel via ECP. Tracing starts automatically on each app launch. Requires Roku OS 15.1+. There is no disable command — enabled channels are cleared on reboot.',
17
+ inputSchema: {
18
+ host: z.string().optional().describe('IP address or hostname of the Roku device'),
19
+ channelId: z
20
+ .string()
21
+ .optional()
22
+ .default('dev')
23
+ .describe('Channel ID to enable tracing for (default: "dev" for sideloaded apps)'),
24
+ },
25
+ }, async (params) => {
26
+ try {
27
+ const host = await resolveHost(params);
28
+ client = new PerfettoClient(host);
29
+ clientHost = host;
30
+ const result = await client.enable(params.channelId ?? 'dev');
31
+ return {
32
+ content: [{ type: 'text', text: jsonify(result) }],
33
+ };
34
+ }
35
+ catch (error) {
36
+ return {
37
+ content: [{ type: 'text', text: `Enable failed: ${friendlyError(error)}` }],
38
+ isError: true,
39
+ };
40
+ }
41
+ });
42
+ // ---------------------------------------------------------------------------
43
+ // roku_perfetto_start
44
+ // ---------------------------------------------------------------------------
45
+ server.registerTool('roku_perfetto_start', {
46
+ description: 'Start recording a Perfetto trace from the Roku device. Opens a binary WebSocket to stream trace data to a local file. Only one recording session at a time. Call roku_perfetto_enable first.',
47
+ inputSchema: {
48
+ host: z.string().optional().describe('IP address or hostname of the Roku device'),
49
+ channelId: z.string().optional().default('dev').describe('Channel ID (default: "dev")'),
50
+ outDir: z.string().optional().describe('Directory to save the trace file (default: OS temp directory)'),
51
+ outFile: z.string().optional().describe('Filename without extension (default: timestamped name)'),
52
+ },
53
+ }, async (params) => {
54
+ try {
55
+ const host = await resolveHost(params);
56
+ if (!client || clientHost !== host) {
57
+ client = new PerfettoClient(host);
58
+ clientHost = host;
59
+ }
60
+ const dir = params.outDir ?? os.tmpdir();
61
+ const name = params.outFile ?? `roku-perfetto-${Date.now()}`;
62
+ const filePath = path.join(dir, `${name}.trace`);
63
+ const session = await client.startRecording(filePath, params.channelId ?? 'dev');
64
+ return {
65
+ content: [{
66
+ type: 'text',
67
+ text: `Recording Perfetto trace on ${session.host} → ${session.filePath}\nUse roku_perfetto_stop to end recording.`,
68
+ }],
69
+ };
70
+ }
71
+ catch (error) {
72
+ return {
73
+ content: [{ type: 'text', text: `Start failed: ${friendlyError(error)}` }],
74
+ isError: true,
75
+ };
76
+ }
77
+ });
78
+ // ---------------------------------------------------------------------------
79
+ // roku_perfetto_stop
80
+ // ---------------------------------------------------------------------------
81
+ server.registerTool('roku_perfetto_stop', {
82
+ description: 'Stop the active Perfetto trace recording. Returns the file path, size, and duration. The resulting .trace file can be opened at https://ui.perfetto.dev/ or analyzed with analyze_perfetto.',
83
+ }, async () => {
84
+ try {
85
+ if (!client || !client.isRecording()) {
86
+ return {
87
+ content: [{ type: 'text', text: 'No active Perfetto recording. Use roku_perfetto_start first.' }],
88
+ isError: true,
89
+ };
90
+ }
91
+ const result = await client.stopRecording();
92
+ return {
93
+ content: [{
94
+ type: 'text',
95
+ text: `Perfetto trace saved: ${result.filePath}\nDuration: ${(result.durationMs / 1000).toFixed(1)}s | Size: ${(result.bytesWritten / 1024).toFixed(1)}KB\nOpen at: https://ui.perfetto.dev/\nOr analyze with: analyze_perfetto`,
96
+ }],
97
+ };
98
+ }
99
+ catch (error) {
100
+ return {
101
+ content: [{ type: 'text', text: `Stop failed: ${friendlyError(error)}` }],
102
+ isError: true,
103
+ };
104
+ }
105
+ });
106
+ // ---------------------------------------------------------------------------
107
+ // analyze_perfetto
108
+ // ---------------------------------------------------------------------------
109
+ server.registerTool('analyze_perfetto', {
110
+ description: 'Analyze a Roku Perfetto trace file (.trace). Returns structured JSON with AI-friendly suggestion fields for each analysis area. Requires the trace_processor binary (auto-downloaded on first use). Modes: summary (full report), frame-drops, key-events, observers, rendezvous, set-fields, threads.',
111
+ inputSchema: {
112
+ filePath: z.string().describe('Absolute path to the .trace file'),
113
+ mode: z
114
+ .enum(['summary', 'frame-drops', 'key-events', 'observers', 'rendezvous', 'set-fields', 'threads'])
115
+ .describe('Analysis mode'),
116
+ top: z.number().optional().describe('Number of entries in ranked lists (default 20)'),
117
+ threshold: z
118
+ .number()
119
+ .optional()
120
+ .describe('Only show entries exceeding this value in milliseconds'),
121
+ },
122
+ }, async (params) => {
123
+ try {
124
+ const opts = { top: params.top ?? 20, threshold: params.threshold };
125
+ const analyzer = await openTrace(params.filePath);
126
+ let report;
127
+ try {
128
+ switch (params.mode) {
129
+ case 'summary':
130
+ report = await analyzer.performanceSummary(opts);
131
+ break;
132
+ case 'frame-drops':
133
+ report = await analyzer.analyzeFrameDrops(opts);
134
+ break;
135
+ case 'key-events':
136
+ report = await analyzer.analyzeKeyEvents(opts);
137
+ break;
138
+ case 'observers':
139
+ report = await analyzer.analyzeObservers(opts);
140
+ break;
141
+ case 'rendezvous':
142
+ report = await analyzer.analyzeRendezvous(opts);
143
+ break;
144
+ case 'set-fields':
145
+ report = await analyzer.analyzeSetFields(opts);
146
+ break;
147
+ case 'threads':
148
+ report = await analyzer.analyzeThreads(opts);
149
+ break;
150
+ }
151
+ }
152
+ finally {
153
+ await analyzer.close();
154
+ }
155
+ return {
156
+ content: [{ type: 'text', text: jsonify(report) }],
157
+ };
158
+ }
159
+ catch (error) {
160
+ return {
161
+ content: [{ type: 'text', text: `Analysis failed: ${friendlyError(error)}` }],
162
+ isError: true,
163
+ };
164
+ }
165
+ });
166
+ // ---------------------------------------------------------------------------
167
+ // compare_perfetto
168
+ // ---------------------------------------------------------------------------
169
+ server.registerTool('compare_perfetto', {
170
+ description: 'Compare two Roku Perfetto trace files to detect performance regressions and improvements. Returns metric deltas for frame drops, key events, observers, rendezvous, and setField calls.',
171
+ inputSchema: {
172
+ beforePath: z.string().describe('Absolute path to the "before" .trace file'),
173
+ afterPath: z.string().describe('Absolute path to the "after" .trace file'),
174
+ top: z.number().optional().describe('Number of entries in ranked lists (default 20)'),
175
+ },
176
+ }, async (params) => {
177
+ try {
178
+ const report = await comparePerfetto(params.beforePath, params.afterPath, {
179
+ top: params.top ?? 20,
180
+ });
181
+ return {
182
+ content: [{ type: 'text', text: jsonify(report) }],
183
+ };
184
+ }
185
+ catch (error) {
186
+ return {
187
+ content: [{ type: 'text', text: `Comparison failed: ${friendlyError(error)}` }],
188
+ isError: true,
189
+ };
190
+ }
191
+ });
192
+ // ---------------------------------------------------------------------------
193
+ // query_perfetto
194
+ // ---------------------------------------------------------------------------
195
+ server.registerTool('query_perfetto', {
196
+ description: 'Run a raw PerfettoSQL query against a Roku Perfetto trace file. Use for custom analysis beyond the built-in modes. Common queries: SELECT * FROM slice WHERE name = \'swapBuffers\' ORDER BY dur DESC; SELECT * FROM slice WHERE name = \'keyEvent\' ORDER BY dur DESC;',
197
+ inputSchema: {
198
+ filePath: z.string().describe('Absolute path to the .trace file'),
199
+ sql: z.string().describe('PerfettoSQL query to execute'),
200
+ },
201
+ }, async (params) => {
202
+ try {
203
+ const analyzer = await openTrace(params.filePath);
204
+ try {
205
+ const result = await analyzer.query(params.sql);
206
+ return {
207
+ content: [{ type: 'text', text: jsonify(result) }],
208
+ };
209
+ }
210
+ finally {
211
+ await analyzer.close();
212
+ }
213
+ }
214
+ catch (error) {
215
+ return {
216
+ content: [{ type: 'text', text: `Query failed: ${friendlyError(error)}` }],
217
+ isError: true,
218
+ };
219
+ }
220
+ });
221
+ }
222
+ //# sourceMappingURL=perfetto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"perfetto.js","sourceRoot":"","sources":["../../src/tools/perfetto.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roku-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "MCP server for Roku device automation — deploy, ECP control, screenshots, debug console, and BrightScript profiler analysis",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,6 +45,7 @@
45
45
  "dotenv": "^17.3.1",
46
46
  "fast-xml-parser": "^5.5.9",
47
47
  "roku-deploy": "^3.16.3",
48
+ "roku-perfetto": "^1.0.0",
48
49
  "zod": "^4.3.6"
49
50
  },
50
51
  "devDependencies": {