Featured

Deploy OpenClaw in 60 seconds — 20% off logoDeploy OpenClaw in 60 seconds — 20% off

Launch OpenClaw on Hostinger in about 60 seconds and keep your agent live 24/7. Our referral link gives you 20% off, no coupon code needed.

Launch on Hostinger
Run your Hermes agent on Hostinger, fully managed logoRun your Hermes agent on Hostinger, fully managed

Launch Hermes on Hostinger in one click, fully managed, no VPS knowledge needed. Use code ZACAARON10 for 10% off.

Launch on Hostinger
Crawl and scrape any site into clean data, 10% off logoCrawl and scrape any site into clean data, 10% off

Firecrawl crawls and scrapes any site into clean markdown for your agent. Get 1,000 free credits, and new users get 10% off their first purchase.

Try Firecrawl free
6,000+ web scrapers for your AI agent, start free logo6,000+ web scrapers for your AI agent, start free

Apify gives your agent live web data: 6,000+ prebuilt scrapers and actors, MCP-ready. Sign up free with $5 in usage credits.

Try Apify free
One API to scrape, enrich, and extract the internet. logoOne API to scrape, enrich, and extract the internet.

Context.dev gives your agents a single API to scrape, enrich, and extract live web data — no proxies, no parsers, no maintenance.

Start building free
SetupClaw: done-for-you OpenClaw for founders & exec teams logoSetupClaw: done-for-you OpenClaw for founders & exec teams

White-glove OpenClaw for founders and exec teams (4–50+ employees): we install, harden, integrate your tools, and maintain it — secured from day one.

Get it set up for you
SEO data APIs for your agent, $1 free credit logoSEO data APIs for your agent, $1 free credit

DataForSEO gives your agent live access to SERP results, keyword data, backlinks, and on-page SEO data through one API. New accounts get a $1 credit, good for up to 20,000 keyword or backlink lookups.

Try DataForSEO free
Reach 48,000+ AI builders

A flat monthly placement in front of developers actively installing AI tools. No lock-in, cancel anytime.

Advertise here

Works with

Claude CodeClaude DesktopCursorVS CodeClineCodex CLIOpenClaw+ any MCP client

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

Enables comprehensive Roku automated testing by combining WebDriver for UI state verification and ECP for device control, allowing validation of acceptance criteria through natural language queries.

README.md

RokuHarness MCP Server

A Model Context Protocol (MCP) server for comprehensive Roku automated testing. RokuHarness combines Roku WebDriver and ECP (External Control Protocol) to provide both UI state verification and device control - enabling true acceptance criteria validation, not just remote control simulation.

Why WebDriver vs ECP?

| Capability | ECP | WebDriver | |-----------|----------------------------------|-----------| | Press remote buttons | ✅ | ✅ | | Launch apps | ✅ | ✅ | | Query UI elements | ❌ | ✅ | | Verify text is on screen | ❌ | ✅ | | Check element attributes | ❌ | ✅ | | Get SceneGraph XML | ❌ | ✅ | | Take screenshots | ❌ | ✅ | | Validate acceptance criteria | ❌ | ✅ |

Bottom line: ECP can press buttons, WebDriver can verify what happens.

Architecture

Your Tests (via any MCP client)
    ↓
RokuHarness MCP Server (this project)
    ↓ (WebDriver HTTP API)
Roku WebDriver Server (from Roku's repo)
    ↓ (ECP + Debug APIs)
Roku Device (your sideloaded channel)

Key Point: The Roku WebDriver Server uses BOTH ECP (for control) and Roku's debug APIs (for UI verification). This MCP server provides a unified interface to both capabilities.

Prerequisites

1. Roku WebDriver Server

You need to download and run Roku's official WebDriver server:

# Clone Roku's automated testing repo
git clone https://github.com/rokudev/automated-channel-testing.git
cd automated-channel-testing

# Build the WebDriver server (requires Go)
cd src
go build

# Run the server
./RokuWebDriver  # Linux/Mac
# or
RokuWebDriver.exe  # Windows

The server will start on http://localhost:9000 by default.

Download pre-built binaries: Check the automated-channel-testing/bin folder for pre-compiled executables.

2. Sideloaded Channel

WebDriver requires your channel to be sideloaded in developer mode:

  1. Enable developer mode on your Roku: Settings → System → About → Press Home 5x, Up, Rewind 2x, Fast Forward 2x
  2. Package your channel as a .zip file
  3. Visit http://YOUR_ROKU_IP in a browser
  4. Upload and install your channel

Important: WebDriver only works with:

  • Sideloaded developer channels (app ID: dev)
  • Channels packaged with your developer account on that specific device
  • SceneGraph-based channels (not legacy BrightScript)

3. This MCP Server

npm install
npm run build

Installation & Setup

1. Build this MCP server

npm install
npm run build

2. Configure Your MCP Client

For Claude Desktop:

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "rokuharness": {
      "command": "node",
      "args": ["/absolute/path/to/rokuharness-mcp/build/index.js"]
    }
  }
}

For Custom MCP Clients:

RokuHarness is MCP-agnostic and works with any MCP client. See "Using Without Claude Desktop" section below for integration examples.

3. Start the Roku WebDriver Server

In a separate terminal:

cd /path/to/automated-channel-testing/bin
./RokuWebDriver  # or RokuWebDriver.exe on Windows

Keep this running while testing.

4. Restart Claude Desktop

The MCP server will now be available in Claude.

Usage Guide

Step 1: Create a Session

Every test session starts by creating a WebDriver session:

Create a Roku WebDriver session:
- WebDriver URL: http://localhost:9000
- Roku IP: 192.168.1.100
- App: dev

This connects to your sideloaded channel.

Step 2: Get UI Source (Critical!)

Before writing any verification queries, inspect the UI:

Get the UI source to see what elements are on screen

You'll get back SceneGraph XML like:

<Scene>
  <Label id="titleLabel" text="Welcome to My App" focused="false" />
  <Button id="loginButton" text="Log In" focused="true" />
  <Poster id="heroImage" uri="https://..." />
</Scene>

This XML shows you:

  • Tags: Component types (Label, Button, Poster, etc.)
  • Attributes: Properties like id, text, focused, visible
  • Hierarchy: Nested structure

Step 3: Verify Elements

Now you can write acceptance criteria tests:

Verify that a Label with text "Welcome to My App" is present on screen
Verify that a Button with id "loginButton" and focused=true is present
Verify that the login button text says "Log In"

Step 4: Navigate and Test

Navigate using these keys: ["Down", "Down", "Select"]
Then verify that a Label with text "Account Settings" appears

Step 5: Run Complete Test Scenarios

Run this acceptance test:

Test: Login Flow
Steps:
1. Press Select to click login button
2. Verify keyboard screen appears (look for Label with text "Enter Email")
3. Send text "test@example.com"
4. Press Down to go to password field
5. Send text "password123"
6. Press Select to submit
7. Verify success message appears
8. Take screenshot

Available Tools

Session Management

create_webdriver_session

Creates a new WebDriver session.

Parameters:

  • webdriver_url (optional): URL of WebDriver server (default: http://localhost:9000)
  • roku_ip (required): IP address of Roku device
  • app (optional): App ID or "dev" for sideloaded (default: "dev")

Example: `` Create a WebDriver session for Roku at 192.168.1.100 ``

end_webdriver_session

Ends the current session and cleans up.

---

UI State Verification (The Key Features!)

get_ui_source

Get the current UI hierarchy as XML or JSON.

Parameters:

  • parsed (optional): Return JSON instead of XML

Example: `` Get the UI source to see current screen structure ``

Returns: ``xml <Scene> <LayoutGroup id="mainLayout"> <Label id="title" text="Home Screen" /> <Button id="playButton" text="Play" focused="true" /> </LayoutGroup> </Scene> ``

This is essential for understanding what elements exist and how to query them.

find_element

Search for a specific element on screen.

Parameters:

  • text (optional): Text content to match
  • tag (optional): SceneGraph component type
  • attributes (optional): Attribute key-value pairs

Examples: `` Find a Label with text "Home Screen" ``

Find a Button with id "playButton"
Find an element with tag "Poster" and attribute uri="https://example.com/image.jpg"

verify_element_present

Check if an element exists (returns true/false).

Parameters:

  • Same as find_element
  • timeout_ms (optional): How long to wait (default: 10000)

Examples: `` Verify a Label with text "Loading..." is present ``

Check if login button is focused: Button with focused=true

verify_screen_loaded

Wait for a specific screen to fully load.

Parameters:

  • screen_marker: Element query that identifies the screen
  • timeout_ms (optional): Maximum wait time

Example: `` Verify the home screen loaded by checking for Label with text "Featured Content" ``

---

Navigation & Input

press_key

Press a single remote button.

Parameters:

  • key: Button name (Home, Back, Up, Down, Left, Right, Select, Play, Pause, etc.)

navigate

Execute a sequence of button presses.

Parameters:

  • keys: Array of keys to press
  • delay_ms (optional): Delay between presses

Example: `` Navigate: Down, Down, Right, Select with 750ms delays ``

send_text

Send text input (for keyboards/forms).

Parameters:

  • text: Text to type

---

Media & Apps

launch_app

Launch an app with optional deep linking.

Parameters:

  • app_id: App ID ("dev" for sideloaded)
  • content_id (optional): Deep link content ID
  • media_type (optional): Type (movie, series, etc.)

get_player_state

Get current playback state.

Returns: Position, duration, state, buffering info

get_installed_apps

List all installed apps.

---

Screenshots

take_screenshot

Capture current screen.

Parameters:

  • save_path (optional): Where to save the image

Example: `` Take a screenshot and save to /tmp/login_screen.png ``

---

Acceptance Testing

run_acceptance_test

Execute a complete test case with multiple steps.

Parameters:

  • test_name: Name of the test
  • steps: Array of test steps

Step types:

  • navigate: Execute key sequence
  • verify_element: Check element is present
  • press_key: Press single key
  • send_text: Type text
  • wait: Pause for duration
  • screenshot: Capture screen

Example: ``` Run this acceptance test:

Name: "Video Playback Test"

Steps:

  1. Action: navigate, Keys: ["Down", "Down", "Select"], Description: "Select first video"
  2. Action: verify_element, Query: {tag: "Video", attributes: {state: "playing"}}, Description: "Verify video is playing"
  3. Action: wait, Duration: 5000, Description: "Let video play for 5 seconds"
  4. Action: press_key, Key: "Pause", Description: "Pause playback"
  5. Action: verify_element, Query: {tag: "Video", attributes: {state: "paused"}}, Description: "Verify video paused"
  6. Action: screenshot, Description: "Capture paused state"

---

## Element Query Syntax

Elements are queried using combinations of:

### By Text

{ "text": "Log In" } ``` Finds elements containing this exact text.

By Tag

{ "tag": "Button" }

Finds elements of this SceneGraph type.

Common tags:

  • Label - Text display
  • Button - Interactive button
  • Poster - Image
  • Video - Video player
  • LayoutGroup - Container
  • RowList - Scrollable list
  • Grid - Grid layout

By Attributes

{
  "attributes": {
    "id": "loginButton",
    "focused": "true"
  }
}

Common attributes:

  • id - Unique identifier
  • focused - Has focus (true/false)
  • visible - Is visible (true/false)
  • text - Text content
  • uri - Image/video URI

Combined Queries

{
  "tag": "Button",
  "text": "Log In",
  "attributes": {
    "focused": "true"
  }
}

Finds a Button with text "Log In" that currently has focus.

---

Real-World Examples

Example 1: Validate Login Screen

1. Create WebDriver session for Roku at 192.168.1.100

2. Get UI source to inspect elements

3. Verify these elements are present:
   - Label with text "Sign In"
   - Button with text "Email Login"
   - Button with text "Guest Mode"

4. Take screenshot for documentation

Example 2: Test Video Playback

Run this acceptance test:

Name: "Video Playback Verification"

Steps:
1. Navigate to content: ["Down", "Down", "Select"]
2. Verify video player loaded: tag=Video
3. Wait 3 seconds for playback to start
4. Verify video is playing: tag=Video, attributes={state: "playing"}
5. Press "Info" to show controls
6. Verify play/pause button visible: tag=Button, text="Pause"
7. Take screenshot of player controls

Example 3: Search Functionality

Test search feature:

1. Press "Search" key
2. Verify keyboard screen: Label with text "Search"
3. Send text "Breaking Bad"
4. Press "Select" to submit
5. Verify results loaded: Label with text "Results for: Breaking Bad"
6. Verify at least one result: tag=Poster (poster images indicate results)

Example 4: Settings Navigation

Navigate to settings and verify:

1. Press Home
2. Navigate: ["Down", "Down", "Down", "Right", "Right", "Select"]
3. Verify settings screen: Label with text "Settings"
4. Navigate: ["Down", "Select"]
5. Verify account screen: Label with text "Account Information"
6. Get UI source to document screen structure

---

Troubleshooting

"No active session"

You must call create_webdriver_session before any other commands.

"WebDriver server not responding"

Ensure the Roku WebDriver server is running: ``bash ./RokuWebDriver ``

"Element not found"

  1. Get the UI source first: get_ui_source
  2. Inspect the actual XML structure
  3. Adjust your query to match actual elements
  4. Check spelling and capitalization (XML is case-sensitive)

"Cannot get source from channel"

  • Only works with sideloaded channels or channels packaged on that device
  • Production channels block source access (security feature)
  • Make sure your channel is running (not on Home screen)

Screenshots not working

Screenshots only work when:

  • Your sideloaded channel is active
  • Developer mode is enabled
  • WebDriver has proper access

Slow queries

  • WebDriver queries can take 500ms-2s depending on complexity
  • Use specific queries (tag + attributes) for faster results
  • Avoid overly broad queries

---

Roku WebDriver Limitations

  1. Sideloaded channels only - Production channels block UI introspection
  2. SceneGraph only - Legacy BrightScript channels not supported
  3. No visual comparisons - You get XML structure, not rendered pixels
  4. Element bounds are relative - Absolute position checking is complex
  5. No direct element interaction - You still navigate with D-pad, not clicks

---

Best Practices

1. Always Inspect First

Get UI source → Understand structure → Write queries

2. Use Specific Queries

❌ { "text": "Play" }  // Might match multiple elements
✅ { "tag": "Button", "id": "mainPlayButton", "text": "Play" }

3. Wait for Screens to Load

verify_screen_loaded with appropriate timeout

4. Test One Thing at a Time

Break complex flows into individual test cases.

5. Take Screenshots

Document state before/after critical steps.

6. Use Acceptance Test Tool

For multi-step scenarios, use run_acceptance_test to get structured results.

---

Integration with CI/CD

This MCP server can be integrated into CI/CD pipelines:

  1. Provision Roku devices in your test lab
  2. Start WebDriver server on each
  3. Run tests via MCP server
  4. Collect results and screenshots
  5. Publish test reports

---

Using Without Claude Desktop

RokuHarness is built on the open MCP standard and works with any MCP client. Here are integration examples:

Python CI/CD Integration

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

# Connect to RokuHarness MCP server
server_params = StdioServerParameters(
    command="node",
    args=["/path/to/rokuharness-mcp/build/index.js"]
)

async with stdio_client(server_params) as (read, write):
    async with ClientSession(read, write) as session:
        await session.initialize()
        
        # Create WebDriver session
        result = await session.call_tool(
            "create_webdriver_session",
            arguments={
                "roku_ip": "192.168.1.100",
                "app": "dev"
            }
        )
        
        # Get UI source
        ui_source = await session.call_tool("get_ui_source", {})
        
        # Verify element
        verify_result = await session.call_tool(
            "verify_element_present",
            arguments={
                "text": "Welcome",
                "tag": "Label"
            }
        )
        
        # Assert in your test framework
        assert verify_result["present"] == True
        
        # Run acceptance test
        test_result = await session.call_tool(
            "run_acceptance_test",
            arguments={
                "test_name": "Login Flow",
                "steps": [
                    {
                        "action": "navigate",
                        "description": "Go to login",
                        "keys": ["Down", "Down", "Select"]
                    },
                    {
                        "action": "verify_element",
                        "description": "Check login screen",
                        "element_query": {"text": "Sign In"}
                    }
                ]
            }
        )
        
        print(f"Test Status: {test_result['summary']['status']}")

Node.js Integration

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

const transport = new StdioClientTransport({
  command: 'node',
  args: ['/path/to/rokuharness-mcp/build/index.js']
});

const client = new Client({
  name: 'roku-test-runner',
  version: '1.0.0'
}, {
  capabilities: {}
});

await client.connect(transport);

// Create session
const session = await client.request({
  method: 'tools/call',
  params: {
    name: 'create_webdriver_session',
    arguments: {
      roku_ip: '192.168.1.100',
      app: 'dev'
    }
  }
});

// Verify element
const result = await client.request({
  method: 'tools/call',
  params: {
    name: 'verify_element_present',
    arguments: {
      tag: 'Button',
      text: 'Play'
    }
  }
});

console.log('Element present:', result.present);

Integration Points

CI/CD Pipelines - Jenkins, GitHub Actions, GitLab CI, CircleCI ✅ Test Frameworks - Jest, Mocha, Pytest, JUnit ✅ QA Platforms - TestRail, Zephyr, qTest, Xray ✅ Custom Dashboards - Build your own test runner UI ✅ Scheduled Testing - Cron jobs, AWS Lambda, Azure Functions ✅ Any MCP-compatible tool - The protocol is completely open

Example GitHub Actions Workflow

name: Roku UI Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
      
      - name: Install dependencies
        run: |
          cd rokuharness-mcp
          npm install
          npm run build
      
      - name: Start Roku WebDriver Server
        run: |
          wget https://github.com/rokudev/automated-channel-testing/releases/download/v1.0/RokuWebDriver
          chmod +x RokuWebDriver
          ./RokuWebDriver &
          
      - name: Run tests
        run: python tests/run_roku_tests.py
        env:
          ROKU_IP: ${{ secrets.ROKU_IP }}
          
      - name: Upload screenshots
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: test-screenshots
          path: screenshots/

---

Comparison with Other Tools

vs Roku Robot Framework

  • This: Natural language via Claude, MCP protocol
  • Robot: Keyword-driven, separate test files

vs Appium Roku Driver

  • This: Direct WebDriver access, simpler setup
  • Appium: Appium ecosystem integration, more tooling

vs Manual Testing

  • This: Automated, repeatable, fast
  • Manual: Comprehensive but slow, expensive

---

Resources

---

Support

For issues or questions:

---

License

MIT License - Free to use for your Roku testing needs.

See related servers & alternatives →

Related MCP servers

Browse all →

Related guides

Hand-picked reading to help you choose and use Files & Docs servers.