@jinlee794/showrunner-mcp
v0.4.3
Published
MCP server for programmatic video generation — storyboard JSON in, MP4 out.
Maintainers
Readme
No Remotion. No subscription. No framework lock-in. Just Playwright frames, GSAP timelines, D3 charts, and ffmpeg encoding.
How It Works
AI Agent ──▶ storyboard JSON ──▶ Showrunner MCP Server
│
Zod validate
├── invalid → errors array
└── valid ──▶ Assemble page per scene
├── Theme CSS
├── Scene HTML (Handlebars)
├── GSAP bundle
└── D3 bundle
│
Playwright (headless Chromium)
│
Scene type?
┌─────────┴─────────┐
chart scenes other scenes
D3 builds SVG Static HTML layout
└─────────┬─────────┘
GSAP timeline (paused)
│
seek(0..1) per frame
Frame capture loop
│
ffmpeg encode
│
Output target
┌─────────┴─────────┐
local Azure
File path Blob URLEach scene template builds a paused GSAP timeline. Chart scenes (chart-bar, chart-line, chart-donut) use D3 to construct SVG elements (scales, axes, paths, arcs) then hand off to GSAP for animation. The renderer scrubs .progress(0..1) for every frame, screenshots, and encodes. Deterministic — same input always produces the same frames.
Prerequisites
- Node.js ≥ 20
- ffmpeg on PATH
- Playwright Chromium (auto-installed on first run)
Install
This package is published to GitHub Packages. Configure npm to use it:
# One-time setup: tell npm where to find @jinlee794 packages
echo "@jinlee794:registry=https://npm.pkg.github.com" >> ~/.npmrc
# Authenticate (use a GitHub PAT with read:packages scope)
npm login --registry=https://npm.pkg.github.com
# Install globally
npm install -g @jinlee794/showrunner-mcp
# Or run directly via npx
npx @jinlee794/showrunner-mcp --helpQuick Start
Use via npx (recommended)
# Render a storyboard to MP4
npx @jinlee794/showrunner-mcp render storyboard.json
# Render with options
npx @jinlee794/showrunner-mcp render storyboard.json --quality high --output output/final.mp4
# Render to GIF
npx @jinlee794/showrunner-mcp render storyboard.json --gif
# Validate a storyboard without rendering
npx @jinlee794/showrunner-mcp validate storyboard.json
# List available scene types
npx @jinlee794/showrunner-mcp scenesUse from source
# Clone and install
git clone https://github.com/JinLee794/Showrunner.git
cd Showrunner
npm install
# Build
npm run build
# Render the demo storyboard
npx showrunner render fixtures/demo-storyboard.jsonShowrunner MCP Server
stdio (local dev)
npx @jinlee794/showrunner-mcpAgent config (VS Code / Claude Desktop / any MCP client):
{
"mcpServers": {
"showrunner": {
"command": "npx",
"args": ["-y", "@jinlee794/showrunner-mcp"],
"env": {
"npm_config_@jinlee794:registry": "https://npm.pkg.github.com"
}
}
}
}HTTP (remote / Azure)
TRANSPORT=http PORT=8080 npx @jinlee794/showrunner-mcpAgent config:
{
"mcpServers": {
"showrunner": {
"url": "https://<your-host>/mcp",
"transport": "http"
}
}
}Tools
| Tool | Description | Returns |
|---|---|---|
| render_video | Render a full storyboard to MP4 (with optional muxed voice-over + VTT transcript) | { path, duration, frames, fileSize, transcriptPath?, narration? } |
| render_gif | Render a storyboard to animated GIF with speed/size control | { path, duration, frames, fileSize } |
| render_scene | Render a single scene to MP4/GIF | { path } |
| preview_storyboard | Generate interactive HTML preview (no Playwright/ffmpeg) | { path } |
| validate_storyboard | Dry-run validation with error reporting | { valid, errors?, summary? } |
| list_scene_types | Dynamically list available scene types + themes from the current install | { sceneTypes: Array<{ type, description, dataSchema }>, themes: string[] } |
| synthesize_narration | Azure Speech TTS → MP3 + VTT/SRT transcript (no video) | { audioPath, transcriptPath, durationSec, scenes[] } |
Scene Types
See the full Scene Catalog → for animated GIF previews, detailed data schemas, and copy-paste sample JSON for every scene type.
| Type | Description |
|---|---|
| title-card | Full-screen branded intro with logo, title, subtitle |
| section-header | Transition slide between sections |
| pipeline-funnel | Horizontal bars with staggered spring animation + count-up values |
| milestone-timeline | Vertical timeline with status dots and owner labels |
| risk-callout | Cards with severity stripes and context text |
| action-items | Numbered checklist with priority indicators |
| deal-team | Avatar grid with role labels |
| kpi-scorecard | 2×2 / 3×2 KPI cards with count-up and trend arrows |
| chart-bar | Animated bar chart with staggered spring |
| chart-line | SVG path draw-on line chart |
| chart-donut | Clockwise-fill donut with center count-up |
| table | Animated row-by-row table |
| quote-highlight | Quote with word-by-word reveal and sentiment accent |
| comparison | Side-by-side comparison with alternating slide-in |
| closing | Branded outro with tagline and CTA |
| code-terminal | Typing terminal with prompt/output/success lines |
| image-card | Full-bleed image with caption overlay and Ken Burns effects |
| bullet-list | Animated bullet list with icons, sub-text, and highlights |
| stat-counter | Big count-up numbers with progress bars and change indicators |
| text-reveal | Cinematic text with typewriter, word-reveal, and highlight effects |
Storyboard Schema
{
"title": "Q2 Pipeline Review",
"theme": "corporate-dark",
"fps": 30,
"resolution": [1920, 1080],
"scenes": [
{
"type": "title-card",
"duration": 4,
"transition": "fade",
"data": {
"title": "Q2 Pipeline Review",
"subtitle": "Enterprise Accounts",
"date": "April 2026"
}
},
{
"type": "kpi-scorecard",
"duration": 6,
"transition": "fade",
"data": {
"kpis": [
{ "label": "Pipeline", "value": 28400000, "unit": "$", "trend": "up", "animateCount": true },
{ "label": "Win Rate", "value": 34, "unit": "%", "trend": "up", "animateCount": true }
]
}
},
{
"type": "closing",
"duration": 3,
"transition": "fade",
"data": { "tagline": "Generated by AI" }
}
]
}See fixtures/sample-storyboard.json and fixtures/demo-storyboard.json for full examples.
Voice-over & Transcripts (Azure Speech)
Announcement: Azure Speech Narration Is Live
Showrunner now ships with first-class speech support. You can add narration directly in storyboard JSON, render MP4 with baked-in voice-over, and automatically generate synced transcript files.
- Azure Speech neural voices (per-scene text or raw SSML)
- Automatic scene timing extension when narration runs longer
- Transcript sidecars in
vtt,srt, or both - Works in CLI and MCP tools (
render_videoandsynthesize_narration)
This is opt-in and environment-authenticated by design: credentials stay in local/server environment variables, never in storyboard payloads.
Opt-in narration. Add a voiceover field to any scene and (optionally) a top-level narration config; Showrunner synthesizes per-scene audio with Azure Speech, concatenates it onto the video timeline, muxes it into the MP4, and writes a synced .vtt transcript sidecar (word-boundary accurate).
Auth is passthrough. The MCP server only reads credentials from its local process environment — never from the storyboard JSON. This keeps keys out of agent context and lets you bring either a subscription key or a short-lived Entra/STS bearer token.
# Option A: subscription key
export AZURE_SPEECH_REGION=eastus
export AZURE_SPEECH_KEY=<your-key>
# Option B: Entra / STS bearer token (10-minute lifetime — refresh on your side)
export AZURE_SPEECH_REGION=eastus
export AZURE_SPEECH_TOKEN=<your-token>
# Optional — custom endpoint (sovereign / private cloud)
export AZURE_SPEECH_ENDPOINT=https://<region>.tts.speech.microsoft.com/
# Install the optional SDK (only needed when narration is enabled)
npm install microsoft-cognitiveservices-speech-sdkStoryboard schema additions
{
"title": "Q2 Pipeline Review",
"narration": {
"voice": "en-US-JennyNeural",
"language": "en-US",
"style": "newscast",
"autoExtendScenes": true,
"gapMs": 150,
"transcriptFormat": "vtt"
},
"scenes": [
{
"type": "title-card",
"duration": 4,
"data": { "title": "Q2 Pipeline Review" },
"voiceover": {
"text": "Here's your Q2 pipeline review — enterprise accounts edition.",
"style": "cheerful",
"rate": "+5%"
}
},
{
"type": "closing",
"duration": 3,
"data": { "tagline": "Generated by AI" },
"voiceover": {
"ssml": "<speak version=\"1.0\" xml:lang=\"en-US\" xmlns:mstts=\"https://www.w3.org/2001/mstts\"><voice name=\"en-US-GuyNeural\"><mstts:express-as style=\"calm\">Thanks for watching.</mstts:express-as></voice></speak>"
}
}
]
}| Field | Purpose |
|---|---|
| narration.voice | Default Azure Neural voice (e.g. en-US-JennyNeural, en-US-AriaNeural) |
| narration.language | BCP-47 locale used when wrapping plain text in SSML |
| narration.style | Default expressive style (newscast, cheerful, chat, …) |
| narration.autoExtendScenes | When narration is longer than scene.duration, extend the scene automatically (default true) |
| narration.gapMs | Silence padding between scenes (default 150) |
| narration.transcriptFormat | vtt (default), srt, both, or none |
| narration.region / narration.endpoint | Override env AZURE_SPEECH_REGION / AZURE_SPEECH_ENDPOINT |
| scene.voiceover.text | Plain text — auto-wrapped in SSML |
| scene.voiceover.ssml | Raw SSML — used verbatim, overrides all other voiceover fields |
| scene.voiceover.voice / style / styleDegree / rate / pitch | Per-scene overrides |
CLI
# Render video with baked-in voice-over + transcript sidecar
AZURE_SPEECH_REGION=eastus AZURE_SPEECH_KEY=... \
npx showrunner render storyboard.json --output out/brief.mp4
# → out/brief.mp4 + out/brief.vttMCP
tool: synthesize_narration
args: { "storyboard": { ... }, "audioOutputPath": "/tmp/brief.mp3" }
→ { audioPath, transcriptPath, durationSec, voice, scenes: [{ sceneStartMs, wordCount, ... }] }Call synthesize_narration first to preview/validate voices, or just call render_video on a storyboard that has voiceover fields — narration is added automatically.
Themes
| Theme | Description |
|---|---|
| corporate-dark | Dark navy, white text, gold accent (default) |
| corporate-light | White background, dark text, blue accent |
| minimal | Near-white, subtle grays |
| microsoft | Microsoft brand guidelines |
| custom | Uses branding overrides from storyboard |
Transitions
Applied between consecutive scenes with a 0.5s overlap:
| Type | Effect |
|---|---|
| cut | Hard cut, no overlap |
| fade | Cross-fade (default) |
| slide-left | Outgoing slides left, incoming slides from right |
| slide-up | Outgoing slides up, incoming slides from below |
| zoom | Outgoing zooms + fades, incoming scales in |
Render Quality
| Quality | Frame format | CRF | Best for |
|---|---|---|---|
| high | PNG | 18 | Final output |
| medium | JPEG 90% | 23 | Default / preview |
| fast | JPEG 70% | 28 | Iteration |
Technology Stack
| Layer | Technology | Role | |---|---|---| | Data visualization | D3.js v7 | SVG chart construction — scales, axes, bars, arcs, line paths | | Animation | GSAP v3 | Timeline-based motion — springs, staggers, easing, count-up | | Templating | Handlebars | Scene HTML generation from storyboard data | | Browser | Playwright | Headless Chromium for deterministic frame capture | | Encoding | ffmpeg | H.264 MP4 and GIF output | | Validation | Zod | Storyboard schema validation | | Server | MCP SDK | stdio + HTTP tool server for AI agents |
Animation System
GSAP (GreenSock) drives all motion. Each scene template builds a paused gsap.timeline() with springs, staggers, easing, and count-up animations. The renderer calls window.__seek(progress) per frame — fully deterministic, no requestAnimationFrame, no timing jitter.
Key GSAP features used: stagger, back.out / elastic.out / power3.out easing, snap: { textContent: 1 } for count-up, nested timeline composition.
D3 + GSAP: How Charts Work
Chart scenes use a two-library pattern — D3 for construction, GSAP for animation:
Renderer ──▶ Playwright Page: setContent(base.html + scene.html)
│ (Page loads GSAP + D3 bundles)
│
├──▶ D3: Parse embedded JSON data block
│ D3: Build scales (scaleLinear, scaleBand, scaleOrdinal)
│ D3: Generate geometry (rects, arcs, line paths)
│ D3: Append SVG elements to DOM
│ (D3 is done — no further re-renders)
│
├──▶ GSAP: __buildTimeline() returns paused gsap.timeline()
│ GSAP: Target D3-generated SVG elements
│ (scaleY, strokeDashoffset, opacity, textContent snap)
│
└──▶ Loop: every frame (30 fps × duration)
Renderer → Page: window.__seek(progress)
Page → GSAP: timeline.progress(n)
GSAP → Page: Update SVG attributes
Renderer: Screenshot to frame buffer
│
Pipe frames to ffmpegThis separation keeps D3 doing what it's best at (data → geometry mapping) and GSAP doing what it's best at (timeline-scrubbed animation), with no runtime conflicts.
| Chart type | D3 handles | GSAP animates |
|---|---|---|
| chart-bar | x/y scales, axis ticks, bar rects | Staggered scaleY grow + value count-up |
| chart-line | Line generator, area fill, x/y axes | strokeDashoffset draw-on + dot pop-in |
| chart-donut | Arc generator, pie layout, slices | Clockwise stroke draw + center count-up |
Project Structure
src/
├── server.ts ──▶ tools/ ──▶ renderer/
│ ├── index.ts
│ ├── frame-capture.ts ──▶ browser-pool.ts
│ ├── encoder.ts
│ ├── transitions.ts
│ └── preview.ts
├── motion/
│ ├── gsap-bundle.ts
│ ├── d3-bundle.ts
│ └── presets.ts
├── templates/
│ ├── base.html ──▶ scenes/ (21 templates)
│ └── themes/
└── schema/
└── storyboard.ts (Zod)src/
├── server.ts # Showrunner MCP server — stdio + HTTP transports
├── tools/ # MCP tool handlers
├── renderer/
│ ├── index.ts # Main render pipeline
│ ├── frame-capture.ts # Playwright frame loop
│ ├── encoder.ts # ffmpeg encoding
│ ├── transitions.ts # Scene transition compositing
│ ├── browser-pool.ts # Warm browser management
│ └── preview.ts # HTML preview generator
├── output/
│ ├── local.ts # File path output
│ └── blob.ts # Azure Blob Storage output
├── motion/
│ ├── gsap-bundle.ts # GSAP core for page injection
│ ├── d3-bundle.ts # D3.js for page injection (chart scenes)
│ └── presets.ts # Reusable animation presets
├── templates/
│ ├── base.html # Common layout with theme + GSAP + D3 injection
│ └── scenes/ # Per-scene-type HTML templates
├── themes/ # CSS custom property theme files
├── schema/
│ └── storyboard.ts # Zod schemas
└── types/
└── index.tsScripts
npm run build # tsc + copy templates/themes to dist/
npm run dev # tsc --watch
npm start # node dist/server.js
npm test # vitestPublishing
Packages are published automatically to GitHub Packages via GitHub Actions when you push a version tag.
# 1. Bump version (updates package.json and creates a git tag)
npm version patch # or minor / major
# 2. Push the commit and tag — CI publishes to GitHub Packages and creates a GitHub Release
git push --follow-tagsNo extra secrets needed — the workflow uses the built-in GITHUB_TOKEN.
License
MIT
