roku-mcp 1.6.0 → 1.7.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 +119 -26
- package/dist/assets/streamprobe/components/StreamProbe.xml +96 -0
- package/dist/assets/streamprobe/manifest +11 -0
- package/dist/assets/streamprobe/source/main.brs +29 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/stream/charles.d.ts +50 -0
- package/dist/stream/charles.d.ts.map +1 -0
- package/dist/stream/charles.js +268 -0
- package/dist/stream/charles.js.map +1 -0
- package/dist/stream/diagnose.d.ts +54 -0
- package/dist/stream/diagnose.d.ts.map +1 -0
- package/dist/stream/diagnose.js +568 -0
- package/dist/stream/diagnose.js.map +1 -0
- package/dist/stream/error-info.d.ts +50 -0
- package/dist/stream/error-info.d.ts.map +1 -0
- package/dist/stream/error-info.js +168 -0
- package/dist/stream/error-info.js.map +1 -0
- package/dist/stream/live.d.ts +43 -0
- package/dist/stream/live.d.ts.map +1 -0
- package/dist/stream/live.js +258 -0
- package/dist/stream/live.js.map +1 -0
- package/dist/stream/manifest.d.ts +90 -0
- package/dist/stream/manifest.d.ts.map +1 -0
- package/dist/stream/manifest.js +480 -0
- package/dist/stream/manifest.js.map +1 -0
- package/dist/stream/roku-specs.json +94 -0
- package/dist/tools/stream.d.ts +3 -0
- package/dist/tools/stream.d.ts.map +1 -0
- package/dist/tools/stream.js +194 -0
- package/dist/tools/stream.js.map +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server for Roku device automation. Exposes tools for app deployment, ECP remote control, screenshot capture, SceneGraph node inspection, and BrightScript debug console access.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npm install -g roku-mcp
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or run directly with npx (no install required):
|
|
7
|
+
The server ships as a single `npx` command. Install once globally if you prefer, or let your MCP client launch it on demand via `npx -y roku-mcp` — both work.
|
|
12
8
|
|
|
13
9
|
```bash
|
|
14
|
-
|
|
10
|
+
npm install -g roku-mcp # optional, global install
|
|
11
|
+
# or just point your client at: npx -y roku-mcp
|
|
15
12
|
```
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
Configure your AI client below. Every platform uses the same launch line; only the config file and reload step change.
|
|
15
|
+
|
|
16
|
+
<details>
|
|
17
|
+
<summary><strong>Cursor</strong> — agent-mode tools, auto-discovers MCP servers</summary>
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
**Prerequisites:** Cursor 0.45+ with Agent mode.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
**Install:** create `.cursor/mcp.json` in your project root (or `~/.cursor/mcp.json` for global):
|
|
22
22
|
|
|
23
23
|
```json
|
|
24
24
|
{
|
|
@@ -35,14 +35,22 @@ npx roku-mcp
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
Then reload the window (`Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**).
|
|
39
|
+
|
|
40
|
+
**Verify:** open **Cursor Settings → MCP** and confirm the `roku` row shows a green dot. If it appears disabled, click the toggle. In a new agent chat, type `list roku tools` — you should see `roku_deploy`, `roku_screenshot`, etc.
|
|
41
|
+
|
|
42
|
+
**Routing rule (recommended):** the repo also ships an [`AGENTS.md`](./AGENTS.md). Cursor picks it up automatically, so the agent knows the canonical *observe → act → wait → verify* loop and the RTA `uiElementId` gotcha without you having to re-explain it every session.
|
|
43
|
+
|
|
44
|
+
**Troubleshoot:** if the server stays grey/disabled, check `View → Output → MCP Logs` — the most common cause is `ROKU_DEVICE_HOST` unreachable on the LAN. Omit the `env` block entirely to fall back to SSDP auto-discovery.
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
</details>
|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
<details>
|
|
49
|
+
<summary><strong>VS Code (GitHub Copilot)</strong> — agent mode, MCP via <code>.vscode/mcp.json</code></summary>
|
|
44
50
|
|
|
45
|
-
1.
|
|
51
|
+
**Prerequisites:** VS Code 1.99+ with the GitHub Copilot extension and an active Copilot subscription that includes Agent mode.
|
|
52
|
+
|
|
53
|
+
**Install:** create `.vscode/mcp.json` in your project root:
|
|
46
54
|
|
|
47
55
|
```json
|
|
48
56
|
{
|
|
@@ -59,12 +67,21 @@ Requires VS Code **1.99+** with the GitHub Copilot extension.
|
|
|
59
67
|
}
|
|
60
68
|
```
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
Reload the window (`Cmd+Shift+P` / `Ctrl+Shift+P` → **Developer: Reload Window**).
|
|
71
|
+
|
|
72
|
+
**Verify:** open Copilot Chat, switch the chat-mode dropdown to **Agent**, and ask "what roku tools do you have?". You should see the full `roku_*` list. The MCP panel (`Cmd+Shift+P` → **MCP: List Servers**) should show `roku` as `Running`.
|
|
73
|
+
|
|
74
|
+
**Troubleshoot:** if the server fails to start, run `npx -y roku-mcp --help` in a regular terminal first — that confirms Node 18+ and `npx` are working before VS Code launches it.
|
|
75
|
+
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
<details>
|
|
79
|
+
<summary><strong>Claude Desktop</strong> — official Anthropic desktop app</summary>
|
|
80
|
+
|
|
81
|
+
**Prerequisites:** Claude Desktop (latest from claude.ai/download), Node.js 18+ on `PATH`.
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
**Install:** edit your Claude Desktop config:
|
|
66
84
|
|
|
67
|
-
Add to your Claude Desktop config file:
|
|
68
85
|
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
69
86
|
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
70
87
|
|
|
@@ -83,11 +100,20 @@ Add to your Claude Desktop config file:
|
|
|
83
100
|
}
|
|
84
101
|
```
|
|
85
102
|
|
|
86
|
-
|
|
103
|
+
Fully quit and restart Claude Desktop (the menu-bar icon must close — `Cmd+Q` on macOS).
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
**Verify:** open a new chat. The 🛠️ icon in the input bar should list `roku-mcp` with all its tools. Ask "take a Roku screenshot" — Claude will request approval for `roku_screenshot` and run it.
|
|
89
106
|
|
|
90
|
-
|
|
107
|
+
**Troubleshoot:** if no tool icon appears, check `~/Library/Logs/Claude/mcp-server-roku.log` (macOS) — `command not found: npx` means Node isn't on the GUI app's `PATH`. Either install Node via the official `.pkg`, or hard-code the absolute paths: `"command": "/usr/local/bin/node"`, `"args": ["/usr/local/bin/npx", "-y", "roku-mcp"]`.
|
|
108
|
+
|
|
109
|
+
</details>
|
|
110
|
+
|
|
111
|
+
<details>
|
|
112
|
+
<summary><strong>Windsurf</strong> — Codeium's agentic IDE</summary>
|
|
113
|
+
|
|
114
|
+
**Prerequisites:** Windsurf with Cascade enabled.
|
|
115
|
+
|
|
116
|
+
**Install:** create `.windsurf/mcp.json` in your project root (or `~/.codeium/windsurf/mcp_config.json` globally):
|
|
91
117
|
|
|
92
118
|
```json
|
|
93
119
|
{
|
|
@@ -104,18 +130,75 @@ Create `.windsurf/mcp.json` in your project root:
|
|
|
104
130
|
}
|
|
105
131
|
```
|
|
106
132
|
|
|
107
|
-
Reload the window
|
|
133
|
+
Reload the window. In Cascade settings, refresh the MCP server list.
|
|
134
|
+
|
|
135
|
+
**Verify:** ask Cascade "list available MCP tools" — `roku_*` entries should appear.
|
|
136
|
+
|
|
137
|
+
**Troubleshoot:** Windsurf occasionally caches a stale server. If reloads don't pick up edits, fully quit Windsurf and reopen.
|
|
108
138
|
|
|
109
|
-
|
|
139
|
+
</details>
|
|
110
140
|
|
|
111
|
-
|
|
141
|
+
<details>
|
|
142
|
+
<summary><strong>Any MCP-compatible client</strong> (Continue, Cline, Zed, Codex, Claude Code, OpenCode, …)</summary>
|
|
143
|
+
|
|
144
|
+
The server speaks the standard MCP **stdio** transport. Anywhere you can declare:
|
|
112
145
|
|
|
113
146
|
```
|
|
114
147
|
command: npx
|
|
115
148
|
args: ["-y", "roku-mcp"]
|
|
149
|
+
env: { ROKU_DEVICE_HOST: "...", ROKU_DEVICE_PASSWORD: "..." }
|
|
116
150
|
```
|
|
117
151
|
|
|
118
|
-
|
|
152
|
+
…the server runs. Both env vars are optional: omit `ROKU_DEVICE_HOST` to use SSDP auto-discovery on the LAN, and pass `host` / `password` on each tool call to override.
|
|
153
|
+
|
|
154
|
+
The repo-root [`AGENTS.md`](./AGENTS.md) is picked up automatically by Claude Code, OpenCode, Codex CLI, and any other tool that follows the AGENTS.md convention, so those agents inherit the same usage guidance without extra setup.
|
|
155
|
+
|
|
156
|
+
</details>
|
|
157
|
+
|
|
158
|
+
## Agent rules
|
|
159
|
+
|
|
160
|
+
For better agent behavior (the `observe → act → wait → verify` loop, the RTA `uiElementId` gotcha that silently breaks `roku_edit_node`, when to use which tool), the repo ships drop-in rules pre-formatted for each major agent. The body is identical — only the wrapper differs to match what each agent auto-loads.
|
|
161
|
+
|
|
162
|
+
| Agent | File | Install path |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| Cursor | [`rules/cursor/roku-mcp.mdc`](./rules/cursor/roku-mcp.mdc) | `.cursor/rules/roku-mcp.mdc` |
|
|
165
|
+
| Claude Code | [`rules/claude-code/CLAUDE.md`](./rules/claude-code/CLAUDE.md) | `CLAUDE.md` (repo root) |
|
|
166
|
+
| Claude Desktop | [`rules/claude-desktop/roku-mcp.md`](./rules/claude-desktop/roku-mcp.md) | Settings → System Prompt |
|
|
167
|
+
| Windsurf | [`rules/windsurf/roku-mcp.md`](./rules/windsurf/roku-mcp.md) | `.windsurf/rules/roku-mcp.md` |
|
|
168
|
+
| Codex CLI / OpenCode / Aider | [`AGENTS.md`](./AGENTS.md) (repo root) | `AGENTS.md` (repo root) |
|
|
169
|
+
|
|
170
|
+
See [`rules/README.md`](./rules/README.md) for one-line copy commands per agent.
|
|
171
|
+
|
|
172
|
+
## Quickstart
|
|
173
|
+
|
|
174
|
+
Once your client is wired up, these three prompts work out of the box and exercise the main capability groups.
|
|
175
|
+
|
|
176
|
+
**1. Confirm the device is reachable**
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
Use roku-mcp to query my Roku device info and tell me the model, firmware, and IP.
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The agent should call `roku_query_device_info` (and possibly `roku_discover` if no host is configured) and reply in one turn.
|
|
183
|
+
|
|
184
|
+
**2. Drive the UI with screenshots**
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
Press Home, wait 2 seconds, take a screenshot, then move Right twice and
|
|
188
|
+
screenshot again. Describe what changed between the two screenshots.
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This exercises `roku_keypress`, `roku_sleep`, and `roku_screenshot` — the standard *observe → act → wait → verify* loop.
|
|
192
|
+
|
|
193
|
+
**3. Live-edit a running channel (requires RTA — see [Runtime UI Editing](#runtime-ui-editing))**
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
On the currently running dev channel, find the label that shows the page
|
|
197
|
+
title, change its text to "Hello from MCP", verify the change with
|
|
198
|
+
roku_get_value, then change it back.
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The agent will `roku_query_app_ui` to locate the node, grab the `uiElementId`, call `roku_edit_node` + `roku_get_value`, and roll back — proving end-to-end RTA wiring.
|
|
119
202
|
|
|
120
203
|
## Environment Variables
|
|
121
204
|
|
|
@@ -281,6 +364,16 @@ Record, analyze, and compare Perfetto traces from Roku devices. Requires **Roku
|
|
|
281
364
|
|
|
282
365
|
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/).
|
|
283
366
|
|
|
367
|
+
### Stream Diagnosis
|
|
368
|
+
|
|
369
|
+
Figure out why an HLS/DASH stream that plays everywhere else fails on Roku.
|
|
370
|
+
|
|
371
|
+
| Tool | Description |
|
|
372
|
+
|---|---|
|
|
373
|
+
| `roku_diagnose_stream` | Correlate the Video-node `errorInfo`, the manifest (a fetchable `url` or pasted `content`), and an optional Charles/HAR capture into ranked Roku-specific root causes (cause, evidence, confidence, severity, fix, doc link). Optionally `captureLive=true` deploys the bundled StreamProbe harness to capture the device error first. |
|
|
374
|
+
|
|
375
|
+
It detects Roku-specific gotchas such as muxed audio+video in fMP4/CMAF HLS (plays video but silent), video codecs/levels beyond the hardware decoder (e.g. 4K H.264 or 8K HEVC), token/403-gated CDNs, and DRM license rejections. The static manifest checks are treated as hints; when you supply the real device `errorInfo` the diagnosis is corroborated to high confidence.
|
|
376
|
+
|
|
284
377
|
## Requirements
|
|
285
378
|
|
|
286
379
|
- Node.js 18+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
|
|
3
|
+
<!-- StreamProbe scene: plays the stream described by deep-link args and exposes
|
|
4
|
+
the Video node error fields (errorCode/errorMsg/errorInfo) for RTA capture. -->
|
|
5
|
+
|
|
6
|
+
<component name="StreamProbe" extends="Scene">
|
|
7
|
+
|
|
8
|
+
<interface>
|
|
9
|
+
<field id="launchArgs" type="assocarray" onChange="onLaunchArgs" />
|
|
10
|
+
<!-- Mirror fields the diagnoser reads via RTA getValue. -->
|
|
11
|
+
<field id="probeState" type="string" value="idle" />
|
|
12
|
+
<field id="probeErrorCode" type="integer" value="0" />
|
|
13
|
+
<field id="probeErrorMsg" type="string" value="" />
|
|
14
|
+
<field id="probeErrorInfo" type="string" value="" />
|
|
15
|
+
</interface>
|
|
16
|
+
|
|
17
|
+
<script type="text/brightscript">
|
|
18
|
+
<![CDATA[
|
|
19
|
+
|
|
20
|
+
sub init()
|
|
21
|
+
m.video = m.top.findNode("probeVideo")
|
|
22
|
+
m.video.observeField("state", "onVideoState")
|
|
23
|
+
end sub
|
|
24
|
+
|
|
25
|
+
' Re-entrant: launch args may arrive after init.
|
|
26
|
+
sub onLaunchArgs()
|
|
27
|
+
startPlayback(m.top.launchArgs)
|
|
28
|
+
end sub
|
|
29
|
+
|
|
30
|
+
sub startPlayback(args as dynamic)
|
|
31
|
+
if args = invalid then return
|
|
32
|
+
|
|
33
|
+
url = pickArg(args, "input_url", "url")
|
|
34
|
+
if url = invalid or url = "" then return
|
|
35
|
+
|
|
36
|
+
format = pickArg(args, "input_format", "format")
|
|
37
|
+
if format = invalid or format = "" then format = "hls"
|
|
38
|
+
|
|
39
|
+
content = createObject("RoSGNode", "ContentNode")
|
|
40
|
+
content.title = "StreamProbe"
|
|
41
|
+
content.streamFormat = format
|
|
42
|
+
content.url = url
|
|
43
|
+
|
|
44
|
+
keySystem = pickArg(args, "input_drm", "drm")
|
|
45
|
+
licenseUrl = pickArg(args, "input_license_url", "licenseServerURL")
|
|
46
|
+
if keySystem <> invalid and keySystem <> "" then
|
|
47
|
+
drmParams = { KeySystem: keySystem }
|
|
48
|
+
if licenseUrl <> invalid and licenseUrl <> "" then
|
|
49
|
+
drmParams.licenseServerURL = licenseUrl
|
|
50
|
+
end if
|
|
51
|
+
headerName = pickArg(args, "input_license_header_name", invalid)
|
|
52
|
+
headerValue = pickArg(args, "input_license_header_value", invalid)
|
|
53
|
+
if headerName <> invalid and headerName <> "" then
|
|
54
|
+
drmParams.licenseReqHeaders = [ { name: headerName, value: headerValue } ]
|
|
55
|
+
end if
|
|
56
|
+
content.drmParams = drmParams
|
|
57
|
+
end if
|
|
58
|
+
|
|
59
|
+
m.video.content = content
|
|
60
|
+
m.video.setFocus(true)
|
|
61
|
+
m.video.control = "play"
|
|
62
|
+
m.top.probeState = "requested"
|
|
63
|
+
end sub
|
|
64
|
+
|
|
65
|
+
function pickArg(args as object, primary as string, fallback as dynamic) as dynamic
|
|
66
|
+
if args.doesExist(primary) then return args[primary]
|
|
67
|
+
if fallback <> invalid and args.doesExist(fallback) then return args[fallback]
|
|
68
|
+
return invalid
|
|
69
|
+
end function
|
|
70
|
+
|
|
71
|
+
sub onVideoState()
|
|
72
|
+
st = m.video.state
|
|
73
|
+
m.top.probeState = st
|
|
74
|
+
' Mirror to console (port 8085) so the error can be captured without RTA.
|
|
75
|
+
print "[StreamProbe] state changed "; st
|
|
76
|
+
if st = "error" then
|
|
77
|
+
m.top.probeErrorCode = m.video.errorCode
|
|
78
|
+
m.top.probeErrorMsg = m.video.errorMsg
|
|
79
|
+
info = m.video.errorInfo
|
|
80
|
+
print "[StreamProbe] errorCode = "; m.video.errorCode
|
|
81
|
+
print "[StreamProbe] errorMsg = "; m.video.errorMsg
|
|
82
|
+
if info <> invalid then
|
|
83
|
+
m.top.probeErrorInfo = formatJSON(info)
|
|
84
|
+
print "[StreamProbe] errorInfo = "; m.top.probeErrorInfo
|
|
85
|
+
end if
|
|
86
|
+
end if
|
|
87
|
+
end sub
|
|
88
|
+
|
|
89
|
+
]]>
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<children>
|
|
93
|
+
<Video id="probeVideo" width="1920" height="1080" />
|
|
94
|
+
</children>
|
|
95
|
+
|
|
96
|
+
</component>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
' StreamProbe — minimal harness used by roku-mcp's roku_diagnose_stream tool to
|
|
2
|
+
' attempt playback of an arbitrary stream and expose the Video node's error
|
|
3
|
+
' fields for capture over RTA.
|
|
4
|
+
|
|
5
|
+
sub Main(args as dynamic)
|
|
6
|
+
showStreamProbe(args)
|
|
7
|
+
end sub
|
|
8
|
+
|
|
9
|
+
sub showStreamProbe(args as dynamic)
|
|
10
|
+
screen = CreateObject("roSGScreen")
|
|
11
|
+
m.port = CreateObject("roMessagePort")
|
|
12
|
+
screen.setMessagePort(m.port)
|
|
13
|
+
scene = screen.CreateScene("StreamProbe")
|
|
14
|
+
screen.show()
|
|
15
|
+
' vscode_rdb_on_device_component_entry
|
|
16
|
+
|
|
17
|
+
' Forward deep-link launch args (input_url, input_format, input_*) to the scene.
|
|
18
|
+
if args <> invalid
|
|
19
|
+
scene.launchArgs = args
|
|
20
|
+
end if
|
|
21
|
+
|
|
22
|
+
while true
|
|
23
|
+
msg = wait(0, m.port)
|
|
24
|
+
msgType = type(msg)
|
|
25
|
+
if msgType = "roSGScreenEvent"
|
|
26
|
+
if msg.isScreenClosed() then return
|
|
27
|
+
end if
|
|
28
|
+
end while
|
|
29
|
+
end sub
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AAWvB,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,QAAQ,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgBlF"}
|
package/dist/index.js
CHANGED
|
@@ -8,10 +8,11 @@ import { registerConsoleTools } from './tools/console.js';
|
|
|
8
8
|
import { registerBsprofTools } from './tools/bsprof.js';
|
|
9
9
|
import { registerPerfettoTools } from './tools/perfetto.js';
|
|
10
10
|
import { registerEditUiTools } from './tools/edit-ui.js';
|
|
11
|
+
import { registerStreamTools } from './tools/stream.js';
|
|
11
12
|
export default function createServer(_options) {
|
|
12
13
|
const server = new McpServer({
|
|
13
14
|
name: 'roku-mcp',
|
|
14
|
-
version: '1.
|
|
15
|
+
version: '1.7.0',
|
|
15
16
|
});
|
|
16
17
|
registerDeployTools(server);
|
|
17
18
|
registerEcpTools(server);
|
|
@@ -20,6 +21,7 @@ export default function createServer(_options) {
|
|
|
20
21
|
registerBsprofTools(server);
|
|
21
22
|
registerPerfettoTools(server);
|
|
22
23
|
registerEditUiTools(server);
|
|
24
|
+
registerStreamTools(server);
|
|
23
25
|
return server.server;
|
|
24
26
|
}
|
|
25
27
|
const isDirectRun = typeof process !== 'undefined' &&
|
|
@@ -30,7 +32,7 @@ if (isDirectRun) {
|
|
|
30
32
|
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
31
33
|
const server = new McpServer({
|
|
32
34
|
name: 'roku-mcp',
|
|
33
|
-
version: '1.
|
|
35
|
+
version: '1.7.0',
|
|
34
36
|
});
|
|
35
37
|
registerDeployTools(server);
|
|
36
38
|
registerEcpTools(server);
|
|
@@ -39,6 +41,7 @@ if (isDirectRun) {
|
|
|
39
41
|
registerBsprofTools(server);
|
|
40
42
|
registerPerfettoTools(server);
|
|
41
43
|
registerEditUiTools(server);
|
|
44
|
+
registerStreamTools(server);
|
|
42
45
|
const transport = new StdioServerTransport();
|
|
43
46
|
await server.connect(transport);
|
|
44
47
|
})();
|
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;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,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;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,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;IAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,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;QAC5B,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC9B,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5B,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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a Charles Proxy session (or HAR capture) into normalized HTTP entries
|
|
3
|
+
* and classify each request as manifest / segment / license / key / other.
|
|
4
|
+
*
|
|
5
|
+
* Supported export formats:
|
|
6
|
+
* - Charles ".chlsj" (JSON Session File): either a single JSON array of
|
|
7
|
+
* request objects, or newline-delimited JSON (one object per line).
|
|
8
|
+
* - ".har" (HTTP Archive): the standard `{ log: { entries: [...] } }` shape
|
|
9
|
+
* produced by Charles "Export > HAR" and by browser devtools.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: the binary ".chls" format is NOT supported (it is a proprietary binary
|
|
12
|
+
* blob). Callers should instruct the user to export as ".chlsj" or ".har".
|
|
13
|
+
*
|
|
14
|
+
* Charles .chlsj entry shape (varies slightly by version) roughly looks like:
|
|
15
|
+
* {
|
|
16
|
+
* "host": "...", "method": "GET", "path": "/x.m3u8",
|
|
17
|
+
* "scheme": "https", "status": 200,
|
|
18
|
+
* "request": { "header": { "headers": [{ "name", "value" }] }, "body": {...} },
|
|
19
|
+
* "response": { "header": { "headers": [...] }, "body": { "text"|"encoded", "contentType" } },
|
|
20
|
+
* "times": { "totalDuration": 123 }
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
export interface NormalizedHttpEntry {
|
|
24
|
+
url: string;
|
|
25
|
+
host: string;
|
|
26
|
+
path: string;
|
|
27
|
+
method: string;
|
|
28
|
+
status: number;
|
|
29
|
+
reqHeaders: Record<string, string>;
|
|
30
|
+
respHeaders: Record<string, string>;
|
|
31
|
+
/** Decoded response body text when available (manifests, license errors). */
|
|
32
|
+
respBody?: string;
|
|
33
|
+
/** Response content-type, lowercased. */
|
|
34
|
+
mimeType?: string;
|
|
35
|
+
durationMs?: number;
|
|
36
|
+
kind: HttpEntryKind;
|
|
37
|
+
}
|
|
38
|
+
export type HttpEntryKind = 'manifest' | 'segment' | 'license' | 'key' | 'other';
|
|
39
|
+
export interface CharlesSession {
|
|
40
|
+
entries: NormalizedHttpEntry[];
|
|
41
|
+
/** Entries with a non-2xx status or otherwise suspicious response. */
|
|
42
|
+
failing: NormalizedHttpEntry[];
|
|
43
|
+
format: 'chlsj' | 'har';
|
|
44
|
+
notes: string[];
|
|
45
|
+
}
|
|
46
|
+
/** Parse the raw text of a Charles .chlsj or .har file. */
|
|
47
|
+
export declare function parseCharlesText(text: string): CharlesSession;
|
|
48
|
+
/** A request is "failing" if it has a non-2xx status, or an empty body where one is expected. */
|
|
49
|
+
export declare function isFailing(entry: NormalizedHttpEntry): boolean;
|
|
50
|
+
//# sourceMappingURL=charles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"charles.d.ts","sourceRoot":"","sources":["../../src/stream/charles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,sEAAsE;IACtE,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,MAAM,EAAE,OAAO,GAAG,KAAK,CAAC;IACxB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAqKD,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CA4D7D;AAQD,iGAAiG;AACjG,wBAAgB,SAAS,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAQ7D"}
|