MCP server for controlling VLC and listing displays on plum (the MacBook). Exposes proper seek/scrub via VLC's HTTP/Lua interface — AppleScript only does play/pause/next/prev and silently ignores `set current time`.
## Tools
**VLC** (`vlc_*`):
| Tool | Inputs | What it does |
|---|---|---|
| `vlc_status` | — | playing state, time, length, volume, fullscreen, current filename |
| `vlc_play_pause` | — | toggle |
| `vlc_next` / `vlc_previous` | — | playlist nav |
| `display_set_vlc_fullscreen_output` | `displayId?: number, preferTv?: boolean` | set VLC's `macosx-vdev` pref so Cmd-F goes to a specific display (default: first external screen). |
**Media** (`media_*`) — TV-show library + resume:
| Tool | Inputs | What it does |
|---|---|---|
| `media_recents` | `limit?: number` | VLC's recently-played list with per-file position (s) and MRU rank, from the macOS plist. |
| `media_list_shows` | — | scan `MEDIA_ROOTS` (default `~/media`) for SxxEyy-named videos; return shows with episode counts + seasons. |
| `media_resume_show` | `show: string` | find latest-watched ep of show in VLC recents, replace playlist with that ep → end of series. |
| `media_play_show` | `show: string, season?: number, episode?: number` | replace playlist with show from given S/E (default S1E1) → end. |
VLC's plist is the source of truth for "where did we leave off" — no parallel state store. Show matching is case-insensitive substring against directory names (with release-group/year/codec noise stripped).
**Black TV** (`black_*`) — the HDMI TV physically attached to **black** (the media server), driven by mpv straight to the DRM console (no X). Unlike `vlc_*` (plum's VLC), these play black's *local*`/bigdisk` library, so they work even when plum is off-LAN / NFS is down. One long-lived mpv is controlled over its IPC socket, so volume/seek/pause never restart playback.
| `black_play_show` | `show: string, season?, episode?` | resolve a show under black's `tv/cartoons/anime` (prefers a 1080p release), build an ordered playlist, play **from the start** to the end |
| `black_resume_show` | `show: string` | **continue watching** — resume the exact episode + second last stopped (falls back to start if no saved position) |
**Persistence.** An mpv Lua hook ([`src/blacktv/black-tv-watch.lua`](src/blacktv/black-tv-watch.lua), deployed to `/usr/local/share/black-tv/`) records a `play` event per episode to a black-local watch log and snapshots the current position into a per-show `resume.json` (`$XDG_STATE_HOME/black-tv/`, i.e. lilith's home on black). `black_resume_show` reads that map and the hook **self-seeks the first file** to the saved second — so resume never leaks into later episodes (a global `--start` would). This state is **black-local on purpose**: it is read over SSH, never written to plum's log over NFS (the flakiest link). `black_play_show` uses `--no-resume-playback`, so deliberate restarts always begin at 0.
All black-side logic lives in [`src/blacktv/black-tv.sh`](src/blacktv/black-tv.sh) (deployed to `/usr/local/bin/black-tv` on black); the TS layer just SSHes to it (`lilith@10.9.0.4`), mirroring how `transmission_*` wraps `transmission-remote`. The script brings up the GPU driver on demand (nouveau, atomic KMS) since black boots headless. **No HDMI-CEC** — the TV must be powered on by hand. Deploy/update with: