年份-MCP
](https://www.npmjs.com/package/@danecodes/roku-mcp)  
MCP服务器和CLI,允许AI代理(和开发人员)与Roku设备交互。
您的编码代理可以看到Roku屏幕上的内容,发送远程控制输入,并查询设备状态——所有这些都可以通过 模型上下文协议 或者一个简单的CLI。
它的作用
- 检查UI --查询SceneGraph节点树,截取屏幕截图,使用类似CSS的选择器查找元素
- 发送输入 --遥控键、文本输入
- 发射和副载 --启动频道,深度链接到内容,部署开发版本
- 测试 --烟雾测试、飞行前证书检查、CPU采样、控制台错误监控
- 查询状态 --设备信息、媒体播放器、活动应用程序、已安装应用程序、调试控制台
- 应用程序上下文 --下降a
roku-app.md项目中的文件,每个连接的代理都会学习应用程序的导航
配置
设置您的Roku设备IP地址:
- MCP服务器: 集
ROKU_DEVICE_IPMCP配置中的环境变量(见下文) - CLI: 使用
--device标志(默认为192.168.0.30) - 屏幕截图: 集
ROKU_DEV_PASSWORD如果您的开发密码不是rokudev
应用程序上下文(可选)
丢一个 roku-app.md 项目根目录中的文件,教代理如何导航您的特定应用程序:
## Crunchyroll Navigation
Left-side nav menu: Home, Browse, Simulcasts, My Lists, Settings.
Press Left from any content screen to open it.
Browse has genre rows — Select a genre to see the content grid.
Search is accessible from the top of the nav menu.MCP服务器在启动时读取此文件,并将其附加到发送给每个连接的代理的指令中。不需要配置——如果文件存在,就使用它。
要使用其他路径,请设置 ROKU_APP_CONTEXT 环境变量:
{
"env": {
"ROKU_APP_CONTEXT": "/path/to/my-app-context.md"
}
}快速开始
作为MCP服务器(Claude Code、Cursor、Windsurf等)
添加到您的 .mcp.json (项目根或 ~/.claude/.mcp.json 全球):
{
"mcpServers": {
"roku": {
"type": "stdio",
"command": "npx",
"args": ["-y", "--package", "@danecodes/roku-mcp", "roku-mcp-server"],
"env": {
"ROKU_DEVICE_IP": "192.168.0.30"
}
}
}
}要自动批准所有Roku工具调用(这样您就不会每次都收到提示),请添加 ~/.claude/settings.json:
{
"permissions": {
"allow": ["mcp__roku"]
}
}作为MCP服务器(GitHub Copilot/VS代码)
Copilot需要HTTP传输。首先在终端中启动服务器:
ROKU_DEVICE_IP=192.168.0.30 npx --package @danecodes/roku-mcp roku-mcp-http
# roku-mcp HTTP server running at http://localhost:3141/mcp然后添加到 .vscode/mcp.json 在您的项目中:
{
"servers": {
"roku": {
"type": "http",
"url": "http://localhost:3141/mcp"
}
}
}在自定义端口上运行 ROKU_MCP_PORT=8888.
作为MCP服务器(OpenAI Codex CLI)
添加 ~/.codex/config.toml (全球)或 .codex/config.toml (项目范围):
[mcp_servers.roku]
command = "npx"
args = ["-y", "--package", "@danecodes/roku-mcp", "roku-mcp-server"]
[mcp_servers.roku.env]
ROKU_DEVICE_IP = "192.168.0.30"您的代理现在拥有以下工具:
设备控制
| 工具 | 说明 |
|---|---|
roku_ui_tree | 获取完整的SceneGraph UI树——查看屏幕上的内容 |
roku_find_element | 通过类似CSS的选择器查找元素 |
roku_press_key | 发送遥控器按键(选择、向上、向下等) |
roku_type_text | 在键盘输入中键入文本 |
roku_screenshot | 截图,可选择保存到磁盘 |
roku_launch | 启动一个带有可选深度链接参数的频道 |
roku_deep_link | 通过ID直接深度链接到内容 |
roku_close_app | 关闭正在运行的应用程序(按Home) |
roku_sideload | 将.zip包部署到设备 |
roku_device_info | 获取设备型号、软件版本、网络信息 |
roku_active_app | 获取当前正在运行的应用程序 |
roku_media_player | 获取播放状态(位置、持续时间、格式) |
roku_installed_apps | 列出所有已安装的频道 |
roku_console_log | 读取BrightScript调试控制台输出(错误、打印语句、崩溃) |
roku_console_command | 发送调试命令(bt、var、cont、step、over、out) |
roku_volume | 调高、调低或静音 |
roku_input | 将自定义输入参数发送到正在运行的应用程序 |
试运行器(左移)
| 工具 | 说明 |
|---|---|
roku_wait_for | 轮询,直到屏幕上出现具有可配置超时的选择器——在导航后使用 |
roku_assert_element | 断言元素存在、聚焦或具有特定属性值——返回通过/失败JSON |
roku_sideload_and_watch | 侧载zip+监视控制台以查看错误/崩溃——返回CI就绪通过/失败报告 |
roku_smoke_test | 启动应用程序,验证UI渲染,可选地验证播放——完整通过/失败,包括步骤详细信息 |
代理效率
| 工具 | 说明 |
|---|---|
roku_focused_element | 仅返回当前聚焦的元素——全树扫描的令牌高效替代方案 |
roku_screen_name | 从SceneGraph根组件推断当前屏幕名称 |
roku_console_watch | 在时间窗口内监控模式匹配的控制台——匹配行通过/失败 |
左移质量门
| 工具 | 说明 |
|---|---|
roku_cert_preflight | 运行Roku证书故障检查表(返回导航、主退出、重新启动、错误扫描) |
roku_chanperf_sample | 通过chanperf在可配置的持续时间内采样CPU使用情况——高水印+通过/失败 |
作为CLI
# Inspect the UI tree
npx roku-mcp ui tree --device 192.168.0.30
npx roku-mcp ui tree --depth 4
npx roku-mcp ui tree --all-attrs
# Find specific elements
npx roku-mcp ui find "HomePage HomeHeroCarousel"
npx roku-mcp ui find "AppLabel" --all-attrs
# See what's focused, what screen you're on, or get raw XML
npx roku-mcp ui focused
npx roku-mcp ui screen
npx roku-mcp ui source
# Take a screenshot
npx roku-mcp ui screenshot -o screen.png
# Send key presses
npx roku-mcp press Select
npx roku-mcp press Down --times 3 --delay 200
# Type text
npx roku-mcp type "search query"
# Launch apps
npx roku-mcp launch dev
npx roku-mcp launch dev --params '{"contentId":"12345","mediaType":"episode"}'
# Device info
npx roku-mcp info device
npx roku-mcp info app
npx roku-mcp info apps
npx roku-mcp info player
# Debug console
npx roku-mcp console log
npx roku-mcp console log --filter "error"
npx roku-mcp console send bt
# Test runner
npx roku-mcp test sideload-watch ./build.zip --duration 30000
npx roku-mcp test smoke --content-id GG5H5XQ7R --media-type episode
npx roku-mcp test cert-preflight
npx roku-mcp test chanperf --duration 15000 --threshold 70
npx roku-mcp test wait "HomeScreen" --timeout 10000
npx roku-mcp test assert "#playButton" --assertion focused作为一个图书馆
ECP客户端单独发布为 @danecodes/roku-ecp --如果你想用自己的代码控制Roku设备,请使用它:
import { EcpClient, Key, parseUiXml, findElement } from '@danecodes/roku-ecp';
const roku = new EcpClient('192.168.0.30');
// Send keys
await roku.press(Key.Down, { times: 3 });
await roku.press(Key.Select);
// Inspect the UI
const xml = await roku.queryAppUi();
const tree = parseUiXml(xml);
const button = findElement(tree, 'AppButton#play_button');
console.log(button?.attrs.focused); // "true"
console.log(button?.attrs.text); // "Play"
// Query state
const player = await roku.queryMediaPlayer();
const app = await roku.queryActiveApp();选择器语法
使用类似CSS的选择器对SceneGraph节点名称查找元素:
| 选择器 | 匹配项 |
|---|---|
HomePage | 带标签的节点 HomePage |
AppButton#play_button | AppButton 随着 name="play_button" |
#titleLabel | 任何节点 name="titleLabel" |
HomePage HomeHeroCarousel | HomeHeroCarousel 里面的任何地方 HomePage |
LayoutGroup > AppLabel | AppLabel 这是一个直接的孩子 LayoutGroup |
AppButton:nth-child(1) | 首先 AppButton 兄弟姐妹之间 |
CollectionModule + CollectionModule | CollectionModule 紧接着另一个 |
A ~ B | 一般兄弟姐妹——全部 B 之后 A |
[focused="true"] | 属性值匹配 |
[text*="Log"] | 属性包含子字符串 |
[text^="Episode"] | 属性以开头 |
[uri$=".png"] | 属性以结尾 |
[visible] | 属性存在 |
AppButton:has(AppLabel[text="Play"]) | 包含匹配子项的父项 |
AppButton:not([focused="true"]) | 否定 |
:first-child, :last-child, :only-child | 位置伪类 |
:empty | 没有子节点的节点 |
LinearCard, SlantedCard | 逗号组--匹配其中之一 |
* | 通用--匹配任何节点 |
烟雾测试演示
将编码代理指向开发版本,它将进行侧载、测试和报告——对输出进行截图并将其发送给您的经理。
You: Sideload the build at ./target/crunchyroll-dev.zip and run a smoke test.
Deep link to content ID "GG5H5XQ7R" (episode) and verify playback starts.
Agent: I'll sideload the build and watch the console for errors first.
→ roku_sideload_and_watch(zip_path: "./target/crunchyroll-dev.zip", duration: 30000)
{
"passed": true,
"message": "PASS: Install Success — no errors in 30s of console output",
"sideload_result": "Install Success",
"errors": [],
"crashes": [],
"exceptions": []
}
Agent: Clean install. Now running the smoke test with playback verification.
→ roku_smoke_test(
content_id: "GG5H5XQ7R",
media_type: "episode",
ui_timeout: 15000,
playback_timeout: 30000
)
{
"passed": true,
"message": "PASS: App launched, UI rendered, and playback started",
"steps": [
{ "name": "launch", "passed": true, "message": "Deep linked to \"GG5H5XQ7R\" in channel dev" },
{ "name": "ui_visible", "passed": true, "message": "UI rendered after 2340ms", "elapsed_ms": 2340 },
{ "name": "playback", "passed": true, "message": "Player reached \"play\" after 8710ms", "elapsed_ms": 8710 }
],
"player_state": {
"state": "play",
"error": false,
"position": "00:00:08",
"duration": "00:42:17"
}
}
Agent: All green. Build sideloaded cleanly, UI rendered in 2.3s, playback started in 8.7s.所有测试命令在失败时都会以代码1退出,使其对CI友好。
在聊天中使用它
您可以通过AI助手与Roku对话。代理使用MCP工具导航、检查和截图设备:
You: Go to the browse screen, select the Action genre, and take a screenshot for me
Agent: I'll take a screenshot to see where we are first.
→ roku_screenshot()
Agent: We're on the Home screen. I'll open the nav menu and find Browse.
→ roku_press_key(key: "Left")
→ roku_screenshot()
Agent: Nav menu is open. Browse is two items down.
→ roku_press_key(key: "Down", times: 2)
→ roku_press_key(key: "Select")
→ roku_screenshot()
Agent: We're on Browse. I can see genre rows — Action is 4 rows down.
→ roku_press_key(key: "Down", times: 4)
→ roku_press_key(key: "Select")
→ roku_screenshot(save_path: "./screenshots/action_genre.png")
Agent: Done — screenshot saved. We're on the Action genre grid.代理通过截图查看屏幕,决定按下什么键,并验证结果——所有这些都是通过自然对话完成的。
运作原理
关键端点是 GET /query/app-ui 它以XML格式返回完整的SceneGraph节点树。roku-ecp解析该XML,并允许您使用类似CSS的选择器进行查询。
需求
- 同一网络上处于开发者模式的Roku设备
- Node.js 18+
许可证
麻省理工学院
