roku-mcp 1.3.3 → 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
@@ -197,6 +197,33 @@ Or simply add the two expected variables to your `.env` alongside your existing
197
197
  | `roku_console_send` | Send a command to the debug console and auto-disconnect |
198
198
  | `roku_console_disconnect` | Close the console connection (safety net) |
199
199
 
200
+ ### BrightScript Profiler
201
+
202
+ Analyze `.bsprof` files generated by Roku devices. These tools use [bsprof-cli](https://www.npmjs.com/package/bsprof-cli) to parse the binary profiler format and return structured JSON reports.
203
+
204
+ | Tool | Description |
205
+ |---|---|
206
+ | `analyze_bsprof` | Analyze a .bsprof file — memory leaks, CPU hot paths, full report, or summary. Supports filtering by module/file and sorting options. |
207
+ | `compare_bsprof` | Compare two .bsprof profiles to detect regressions, improvements, new leaks, and resolved leaks. |
208
+ | `bsprof_info` | Get header metadata (target name, device, firmware, format version, features) without full parsing. |
209
+
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
+
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
+
200
227
  ## Requirements
201
228
 
202
229
  - Node.js 18+
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AAOvB,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAAQ,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAYlF"}
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
@@ -5,15 +5,19 @@ import { registerDeployTools } from './tools/deploy.js';
5
5
  import { registerEcpTools } from './tools/ecp.js';
6
6
  import { registerScreenshotTools } from './tools/screenshot.js';
7
7
  import { registerConsoleTools } from './tools/console.js';
8
+ import { registerBsprofTools } from './tools/bsprof.js';
9
+ import { registerPerfettoTools } from './tools/perfetto.js';
8
10
  export default function createServer(_options) {
9
11
  const server = new McpServer({
10
12
  name: 'roku-mcp',
11
- version: '1.3.3',
13
+ version: '1.5.0',
12
14
  });
13
15
  registerDeployTools(server);
14
16
  registerEcpTools(server);
15
17
  registerScreenshotTools(server);
16
18
  registerConsoleTools(server);
19
+ registerBsprofTools(server);
20
+ registerPerfettoTools(server);
17
21
  return server.server;
18
22
  }
19
23
  const isDirectRun = typeof process !== 'undefined' &&
@@ -24,12 +28,14 @@ if (isDirectRun) {
24
28
  const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
25
29
  const server = new McpServer({
26
30
  name: 'roku-mcp',
27
- version: '1.3.3',
31
+ version: '1.5.0',
28
32
  });
29
33
  registerDeployTools(server);
30
34
  registerEcpTools(server);
31
35
  registerScreenshotTools(server);
32
36
  registerConsoleTools(server);
37
+ registerBsprofTools(server);
38
+ registerPerfettoTools(server);
33
39
  const transport = new StdioServerTransport();
34
40
  await server.connect(transport);
35
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;AAE1D,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;IAE7B,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;QAE7B,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 registerBsprofTools(server: McpServer): void;
3
+ //# sourceMappingURL=bsprof.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsprof.d.ts","sourceRoot":"","sources":["../../src/tools/bsprof.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsCpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6I3D"}
@@ -0,0 +1,140 @@
1
+ import { z } from 'zod';
2
+ import { readFileSync } from 'fs';
3
+ import { parseBsprof, analyzeMemory, analyzeCpu, compareBsprof, buildHeaderInfo, buildParseStats, aggregate, rankFunctions, } from 'bsprof-cli';
4
+ import { friendlyError } from '../roku-config.js';
5
+ function jsonify(value) {
6
+ return JSON.stringify(value, (_k, v) => (typeof v === 'bigint' ? Number(v) : v), 2);
7
+ }
8
+ function buildOptions(params) {
9
+ return {
10
+ top: params.top ?? 30,
11
+ sortBy: params.sortBy,
12
+ filterModule: params.filterModule,
13
+ excludeModule: params.excludeModule,
14
+ filterFile: params.filterFile,
15
+ threshold: params.threshold ?? 0,
16
+ };
17
+ }
18
+ export function registerBsprofTools(server) {
19
+ server.registerTool('analyze_bsprof', {
20
+ description: 'Analyze a BrightScript Profiler (.bsprof) file from a Roku device. Supports memory leak detection, CPU hot-path analysis, combined full reports, and one-page summaries. Returns structured JSON.',
21
+ inputSchema: {
22
+ filePath: z.string().describe('Absolute path to the .bsprof file'),
23
+ mode: z
24
+ .enum(['memory', 'cpu', 'full', 'summary'])
25
+ .describe('Analysis mode: memory (retained bytes, leaks), cpu (self time, hot functions), full (both), summary (key metrics overview)'),
26
+ top: z.number().optional().describe('Number of entries in ranked lists (default 30)'),
27
+ sortBy: z
28
+ .string()
29
+ .optional()
30
+ .describe('Sort field: retained, allocated, allocCount, cpuSelf, wallSelf, callCount'),
31
+ filterModule: z.string().optional().describe('Filter results to a specific module/thread'),
32
+ excludeModule: z.string().optional().describe('Exclude a module (e.g. roku_ads_lib)'),
33
+ filterFile: z.string().optional().describe('Glob pattern for file filtering'),
34
+ threshold: z
35
+ .number()
36
+ .optional()
37
+ .describe('Only show entries exceeding this value (bytes for memory, microseconds for CPU)'),
38
+ },
39
+ }, async (params) => {
40
+ try {
41
+ const buffer = readFileSync(params.filePath);
42
+ const profile = parseBsprof(buffer);
43
+ const options = buildOptions(params);
44
+ let report;
45
+ switch (params.mode) {
46
+ case 'memory':
47
+ report = analyzeMemory(profile, options);
48
+ break;
49
+ case 'cpu':
50
+ report = analyzeCpu(profile, options);
51
+ break;
52
+ case 'full': {
53
+ const memory = analyzeMemory(profile, options);
54
+ const cpu = analyzeCpu(profile, options);
55
+ const fullReport = { header: memory.header, memory, cpu };
56
+ report = fullReport;
57
+ break;
58
+ }
59
+ case 'summary': {
60
+ const header = buildHeaderInfo(profile);
61
+ const parseStats = buildParseStats(profile);
62
+ const agg = aggregate(profile, options);
63
+ const allFuncs = [...agg.byFunction.values()];
64
+ const summaryReport = {
65
+ header,
66
+ parseStats,
67
+ topMemoryLeaks: rankFunctions(allFuncs, 'retained', options.top),
68
+ topCpuConsumers: rankFunctions(allFuncs, 'cpuSelf', options.top),
69
+ moduleOverview: [...agg.byModule.values()],
70
+ };
71
+ report = summaryReport;
72
+ break;
73
+ }
74
+ }
75
+ return {
76
+ content: [{ type: 'text', text: jsonify(report) }],
77
+ };
78
+ }
79
+ catch (error) {
80
+ return {
81
+ content: [{ type: 'text', text: `Analysis failed: ${friendlyError(error)}` }],
82
+ isError: true,
83
+ };
84
+ }
85
+ });
86
+ server.registerTool('compare_bsprof', {
87
+ description: 'Compare two BrightScript Profiler (.bsprof) files to detect regressions, improvements, new memory leaks, resolved leaks, and CPU time deltas between runs.',
88
+ inputSchema: {
89
+ beforePath: z.string().describe('Absolute path to the "before" .bsprof file'),
90
+ afterPath: z.string().describe('Absolute path to the "after" .bsprof file'),
91
+ top: z.number().optional().describe('Number of entries in ranked lists (default 30)'),
92
+ filterModule: z.string().optional().describe('Filter results to a specific module/thread'),
93
+ excludeModule: z.string().optional().describe('Exclude a module (e.g. roku_ads_lib)'),
94
+ threshold: z
95
+ .number()
96
+ .optional()
97
+ .describe('Only show deltas exceeding this value (bytes or microseconds)'),
98
+ },
99
+ }, async (params) => {
100
+ try {
101
+ const beforeBuf = readFileSync(params.beforePath);
102
+ const afterBuf = readFileSync(params.afterPath);
103
+ const before = parseBsprof(beforeBuf);
104
+ const after = parseBsprof(afterBuf);
105
+ const options = buildOptions(params);
106
+ const diff = compareBsprof(before, after, params.beforePath, params.afterPath, options);
107
+ return {
108
+ content: [{ type: 'text', text: jsonify(diff) }],
109
+ };
110
+ }
111
+ catch (error) {
112
+ return {
113
+ content: [{ type: 'text', text: `Comparison failed: ${friendlyError(error)}` }],
114
+ isError: true,
115
+ };
116
+ }
117
+ });
118
+ server.registerTool('bsprof_info', {
119
+ description: 'Get header metadata from a .bsprof file without full analysis — target name, device model, firmware, format version, duration, file size, and enabled features.',
120
+ inputSchema: {
121
+ filePath: z.string().describe('Absolute path to the .bsprof file'),
122
+ },
123
+ }, async (params) => {
124
+ try {
125
+ const buffer = readFileSync(params.filePath);
126
+ const profile = parseBsprof(buffer);
127
+ const info = buildHeaderInfo(profile);
128
+ return {
129
+ content: [{ type: 'text', text: jsonify(info) }],
130
+ };
131
+ }
132
+ catch (error) {
133
+ return {
134
+ content: [{ type: 'text', text: `Info failed: ${friendlyError(error)}` }],
135
+ isError: true,
136
+ };
137
+ }
138
+ });
139
+ }
140
+ //# sourceMappingURL=bsprof.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bsprof.js","sourceRoot":"","sources":["../../src/tools/bsprof.ts"],"names":[],"mappings":"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"}
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "roku-mcp",
3
- "version": "1.3.3",
4
- "description": "MCP server for Roku device automation — deploy, ECP control, screenshots, and debug console",
3
+ "version": "1.5.0",
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",
7
7
  "module": "src/index.ts",
@@ -41,9 +41,11 @@
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.28.0",
43
43
  "axios": "^1.13.6",
44
+ "bsprof-cli": "^1.0.0",
44
45
  "dotenv": "^17.3.1",
45
46
  "fast-xml-parser": "^5.5.9",
46
47
  "roku-deploy": "^3.16.3",
48
+ "roku-perfetto": "^1.0.0",
47
49
  "zod": "^4.3.6"
48
50
  },
49
51
  "devDependencies": {