npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

brs-mcp

v0.2.1

Published

MCP server that scaffolds runnable Roku channels from a validated AppSpec.

Downloads

370

Readme

brs-mcp

An MCP server that scaffolds runnable Roku channels (BrightScript + SceneGraph) from a validated AppSpec, zips them, and optionally sideloads them to a Roku in developer mode.

Node License: MIT MCP

What it does

An AI assistant (or any MCP client) passes a strict, versioned AppSpec. The server returns a complete project tree, produces a sideload-ready zip, and optionally installs it on a Roku device. Every generated file comes from a curated, hand-authored, device-tested template; the server never asks an LLM to write BrightScript.

Same spec in, same bytes out, every time. Zips are byte-reproducible across hosts (sorted entries, fixed mtime). Re-running on a machine in a different time zone produces identical output.

For a fuller styled reference with per-tool I/O examples, see docs/index.html.

Install

For a styled walkthrough with prereqs, four numbered steps, and update guidance on one page, see docs/install.html.

npm install -g brs-mcp

Wire it into your MCP client (e.g., Claude Desktop):

{
  "mcpServers": {
    "brs-mcp": {
      "command": "brs-mcp"
    }
  }
}

The server speaks MCP over stdio. Logs go to stderr; stdout is reserved for JSON-RPC.

Verify

One-liner that cold-fetches the published package and exercises a real MCP initialize handshake. No global install required:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}' | npx -y brs-mcp

Expected response on stdout (one JSON object, formatted here for readability):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "brs-mcp", "version": "0.1.0" }
  }
}

Exit code 0, stderr silent. If you see any stdout output that isn't a single JSON-RPC object, something in your shell is polluting the transport; verify with npx -y brs-mcp <<< '{}' 2>/dev/null | head -c 200.

Tools

list_templates

List every template bundled with this server.

  • Input: {}
  • Output: { "templates": [{ "id", "category", "version", "description" }] }

get_template_schema

Return the JSON Schema (Draft 7) for a template's AppSpec, plus a minimal example.

  • Input: { "id": "video_grid_channel" }
  • Output: { "schema": <JSON Schema>, "example_spec": <AppSpec> }

generate_app

Render a Roku channel project from a validated AppSpec. Optionally zip and sideload.

  • Input: { "spec", "output_dir", "assets_root"?, "overwrite"?, "zip"?, "sideload"? }
  • Output (success): { "ok": true, "project_dir", "files_written", "zip_path"?, "sideload"? }
  • sideload implies zip: true (enforced by schema).

package_app

Zip an already-generated project directory into a sideload-ready archive. Validates a top-level manifest. Output is byte-reproducible.

  • Input: { "project_dir", "output_zip"? }
  • Output: { "ok": true, "zip_path", "size_bytes", "entry_count" }

sideload_app

Install a zip on a Roku in developer mode via HTTP Digest-authenticated multipart POST to /plugin_install. dev_password is never logged or echoed.

  • Input: { "zip_path", "device_ip", "dev_password" }
  • Output: { "ok": true, "status": "installed" | "identical", "message", "duration_ms", "raw_html"? }

Templates

screensaver

Roku screensaver channel. Three styles:

  • slideshow: crossfade through bundled images.
  • animated: bouncing colored shapes (no images required).
  • quadrant: 4-up grid of bundled images with rotating cells.
{
  "template": "screensaver",
  "spec_version": 1,
  "app": { "name": "My Screensaver", "major_version": 1, "minor_version": 0, "build_version": 0 },
  "style": "animated"
}

Roku menu label note: When sideloaded via the dev web server, screensavers always appear in Settings -> Theme -> Screensavers as the literal string (dev), not as the app.name value. This is a Roku dev-build UX quirk and applies to every sideloaded screensaver including Roku's own canonical samples. The screensaver_title manifest field IS still required (and is presumably used once the screensaver is published through the channel store), but for sideload-based testing, look for the (dev) entry.

video_grid_channel

VOD grid: home (RowList) → detail → video player. Consumes mRSS, Roku Direct Publisher JSON, or a custom JSON feed.

{
  "template": "video_grid_channel",
  "spec_version": 1,
  "app": { "name": "Example", "major_version": 1, "minor_version": 0, "build_version": 0 },
  "branding": {
    "primary_color": "#E50914",
    "background_color": "#141414",
    "text_color": "#FFFFFF",
    "splash": { "hd": "./splash_hd.png", "fhd": "./splash_fhd.png" },
    "icon": { "hd": "./icon_hd.png", "fhd": "./icon_fhd.png" }
  },
  "content": {
    "feed_url": "https://example.com/feed.json",
    "feed_format": "roku_direct_publisher_json"
  }
}

Highlights: deep-link aware (Main(args) + roInput runtime listener); centralized screen-stack with focus restoration; canonical Roku Overhang brand bar; BusySpinner during feed load; async feed fetch via roUrlTransfer.asyncGetToString.

Error taxonomy

| Code | Meaning | | -------------------------- | ------------------------------------------------------------------- | | SCHEMA_MISMATCH | Tool input or AppSpec failed validation. | | UNKNOWN_TEMPLATE | spec.template is not a registered id. | | UNSUPPORTED_SPEC_VERSION | spec.spec_version is not supported by this server. | | PATH_REFUSED | Path is on the blocklist or outside an allowed directory. | | OUTPUT_DIR_NOT_EMPTY | Target directory exists and is non-empty (overwrite not requested). | | ASSET_NOT_FOUND | A spec-referenced asset (icon/splash/etc.) is missing. | | PROJECT_DIR_INVALID | package_app input is not a valid Roku project directory. | | RENDER_FAILED | EJS render error inside a template. | | WRITE_FAILED | Writer failed during atomic project write. | | ZIP_FAILED | Packager failed. | | ZIP_NOT_FOUND | sideload_app could not find the supplied zip. | | DEVICE_UNREACHABLE | Network failure reaching the Roku. | | DEVICE_NOT_DEV_MODE | Roku is not in developer mode. | | DEVICE_AUTH_FAILED | HTTP Digest auth was rejected. | | SIDELOAD_REJECTED | Roku returned a failure body (e.g., bad archive). | | SIDELOAD_TIMEOUT | Operation timed out before the device responded. |

Every failure response carries { ok: false, stage, code, message, details? }. stage is one of validate, render, write, package, sideload.

Architecture

Strict one-way dependency flow under src/:

  • tools/: MCP handlers. Composition root.
  • templates/: Static template registry + EJS engine + render helpers. No network.
  • build/: Atomic writer + deterministic zip packager (yazl, STORED, forced DOS timestamps). No network.
  • device/: The ONLY module that imports a network client (undici). RFC 2617 Digest auth, multipart streaming via fs.openAsBlob, parsed Roku response markers.
  • spec/: Shared zod schemas + error factory.

Real-device fixtures

test/fixtures/roku-responses/ contains the HTML response bodies the parser is tested against. They are captured against a live Roku via scripts/smoke.ts. Provenance and capture date live in test/fixtures/roku-responses/README.md.

Determinism

No Date.now(), no Math.random(), no clock skew leak into template output or the zip. Same AppSpec produces the same bytes on any host.

Contributing

Run npm install once after cloning. This installs the husky pre-commit hook via the prepare lifecycle script. Without it, your first commit bypasses the lint / format check.

The full local quality gate is:

npm run format:check && npm run lint && npm run typecheck && npm run build && npm test

License

MIT; see LICENSE.