diff --git a/README.md b/README.md index ab639d5..6c6fb89 100644 --- a/README.md +++ b/README.md @@ -2,160 +2,178 @@ -AI-first MCP server for Elgato Stream Deck profile management. The default server writes directly to the Stream Deck desktop app's native profile files, and the original USB-direct server is still available as a legacy fallback. +Tell your AI what kind of Stream Deck you want. Get back a fully authored profile — buttons, icons, colors, dials, and the shell scripts behind them. -## Installation +streamdeck-mcp is the bridge: an MCP server that reads and writes Elgato Stream Deck profiles directly, in the format the desktop app already uses. Themed decks, per-project layouts, app-specific control boards — built in a single prompt instead of an hour in the GUI. Works with Claude Desktop, Claude Code, Cursor, Codex, and any MCP-compatible client. -### Default: Desktop Profile Writer +![A Stream Deck + XL profile showing a Slack control board with around thirty themed buttons, color-coded icons, and six configured dials in the touch strip — all auto-generated by Claude Desktop from a single prompt.](docs/screenshots/slack-control-board.jpg) -The default packaged entrypoint is the profile writer. It edits `ProfilesV3` when present, then falls back to `ProfilesV2`. +> Asked Claude Desktop for *"a Slack control board"* — got back this profile. Buttons, icons, colors, and dials all authored in one shot via the MCP server. -```bash -uvx streamdeck-mcp -``` +## What you can build + +The decks aren't generic. When other MCP servers are loaded — Slack, Home Assistant, OBS, GitHub, Hue, the ones you already use — your AI queries them first to discover *your* channels, *your* devices, *your* scenes, then authors a deck around what's actually there. Icons render to match (~7,400 Material Design Icons bundled offline, or freeform text), so every button looks like it belongs. No Stream Deck SDK, no plugin authoring — just shell scripts and a prompt. + +A few worth trying: + +- ***"Make me a control board for Slack."*** + → Queries your Slack MCP for channels, status, and unread state. Generates one channel-jump button per channel you actually use, status toggles (Active / Away / DND), a Read-All, and dials for unread counts. The screenshot above is one such result. + +- ***"A hello-kitty-themed Home Assistant dashboard for the living room."*** + → Pulls Home Assistant entities scoped to the living room area, then lays them out in pastel kawaii — scenes on row one, lights on row two, media on row three. Palette and icon style follow the theme. + +- ***"OBS control panel based on my actual scenes and audio inputs."*** + → Queries OBS for scenes, sources, and audio devices. Generates scene-switch buttons with the right transitions, source toggles, and dials for per-input gain on the touch strip. + +- ***"A dev deck for this repo in Nordic colors."*** + → Reads your project's scripts (npm, Make, just — whatever's there), recent PRs via the GitHub MCP, and the local docs structure. Drops shell-script buttons for the most-used commands, PR/CI jumps, and a Nordic-palette icon set. + +- ***"A 'Friday demo' deck: open Zoom, mute Slack, set Hue to 'focus', start a screen recording."*** + → Composes across whatever MCPs you have loaded. Writes one shell script per action to `~/StreamDeckScripts/` and wires them to a single page with custom icons. + +Same pattern every time: ask, your AI inventories your hardware and your other MCPs, plans the layout, generates icons, writes the profile files, restarts the Elgato app. Iteration is free — tweak the prompt, get a different deck. -### Local Repo Configuration +## How it works + +1. **Install the MCP server** in your AI client (snippets below). +2. **Install the designer skill** in Claude Code, or invoke the `design_streamdeck_deck` MCP prompt in any other client. +3. **Ask for a deck.** Your AI calls `streamdeck_read_profiles` to inventory the hardware, plans the layout, generates icons (~7,400 Material Design Icons bundled offline, or freeform text), writes the profile files, and restarts the Elgato app so the device picks up the changes. + +## Install + +The packaged entrypoint is `streamdeck-mcp`, run via [`uvx`](https://docs.astral.sh/uv/). It edits the desktop app's `ProfilesV3` files when present and falls back to `ProfilesV2`. + +### Cursor + +[![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=streamdeck&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyJzdHJlYW1kZWNrLW1jcCJdfQ==) + +Or paste into `~/.cursor/mcp.json`: ```json { "mcpServers": { "streamdeck": { - "command": "uv", - "args": [ - "--directory", - "/path/to/streamdeck-mcp", - "run", - "profile_server.py" - ] + "command": "uvx", + "args": ["streamdeck-mcp"] } } } ``` -### Legacy USB Server +### Claude Desktop -If you still want direct hardware control that bypasses the Elgato app entirely, keep using the legacy server: +Paste into `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows), then restart Claude Desktop: ```json { "mcpServers": { - "streamdeck-usb": { - "command": "uv", - "args": [ - "--directory", - "/path/to/streamdeck-mcp", - "run", - "server.py" - ] + "streamdeck": { + "command": "uvx", + "args": ["streamdeck-mcp"] } } } ``` -Or use the packaged legacy entrypoint: +### Claude Code ```bash -uvx --from streamdeck-mcp streamdeck-mcp-usb +claude mcp add streamdeck -- uvx streamdeck-mcp ``` -## Designing Decks with the `streamdeck-designer` Skill - -streamdeck-mcp ships with an **Agent Skill** that teaches Claude how to design, theme, and author complete Stream Deck layouts using the MCP tools below. With the skill loaded, Claude can one-shot an authored deck from a high-level prompt — "give me a hello-kitty-themed Twitch deck with Hue light controls", "build a dev deck for this repo in Nordic colors" — including palette planning, integration discovery, consistent icon generation, and shell-script wiring. +### OpenAI Codex -### Install the skill (Claude Code / Claude.ai) +Add to `~/.codex/config.toml`: -```bash -# After installing streamdeck-mcp (e.g. via uvx or pip install -e .) -streamdeck-mcp-install-skill -# or, without the console script: -uv run python -m install_skill +```toml +[mcp_servers.streamdeck] +command = "uvx" +args = ["streamdeck-mcp"] ``` -The skill is copied to `~/.claude/skills/streamdeck-designer/`. Restart Claude Code (or start a new session) and it auto-loads when your request matches a themed/custom deck design intent. +### Other MCP clients -To upgrade after a `streamdeck-mcp` version bump, rerun with `--force`: +Anything that speaks MCP over stdio works the same way — point it at `uvx streamdeck-mcp`. The JSON snippet above is the canonical shape. -```bash -streamdeck-mcp-install-skill --force -``` +### Note for Linux users -### Other MCP clients +The default profile writer needs the Elgato Stream Deck desktop app, which is macOS- and Windows-only. On Linux, use the legacy USB-direct server instead — see [Legacy USB mode](#legacy-usb-mode) below. + +## The `streamdeck-designer` skill + +streamdeck-mcp ships with an **Agent Skill** that teaches Claude (in Claude Code) how to plan, theme, and author full decks end-to-end. The skill covers: + +- **Hardware inventory** — always calls `streamdeck_read_profiles` first, then matches authoring style to your model. +- **Palette + typography planning** — 8 theme archetypes (kawaii, retrowave, brutalist, nordic, terminal, nature, minimal, corporate) with ready palettes and per-strategy icon-color guidance. +- **Dials + touchstrip** — decision tree for + / + XL encoder layouts (`$X1` / `$A0` / …). +- **Integration recipes** — per-service patterns for Hue, OBS, Spotify, Home Assistant, Twitch, shell, browser. Credentials live in `~/StreamDeckScripts/.env`, never baked into scripts. +- **Starter recipes** — streamer/hello-kitty (+ XL), dev/Nordic (XL), music/retrowave (Original) as adaptation shapes. + +### Install the skill (Claude Code) -Clients that don't load Claude Code skills (Claude Desktop, Cursor, ChatGPT-with-MCP, …) get a condensed mirror via the MCP prompt **`design_streamdeck_deck`**. Invoke it before asking for a deck — most clients expose it as a slash command or prompt picker. Pass the user's intent via the `intent` argument if your client supports it. +```bash +uvx --from streamdeck-mcp streamdeck-mcp-install-skill +``` -### What the skill covers +The skill is copied to `~/.claude/skills/streamdeck-designer/`. Restart Claude Code (or start a new session) and it auto-loads when your request matches a deck-design intent. Re-run with `--force` to upgrade after a version bump. -- Hardware inventory — the skill always calls `streamdeck_read_profiles` first, then consults bundled references to match authoring style to the user's model (Original, MK.2, XL, Plus XL, Neo, Mini). -- Palette + typography planning — 8 theme archetypes (kawaii, retrowave, brutalist, nordic, terminal, nature, minimal, corporate) with ready palettes + per-strategy icon-color guidance. -- Dials + touchstrip — decision tree for Plus XL encoder layouts (`$X1` / `$A0` / …) with Phase 1 constraints called out (value/indicator slots render empty until the live channel lands). -- Integration recipes — per-service patterns for Hue, OBS, Spotify, Home Assistant, Twitch, shell, browser. Authoring-time discovery via companion MCPs; credentials stored in `~/StreamDeckScripts/.env`, never baked into scripts. -- Starter recipes — streamer/hello-kitty (Plus XL), dev/Nordic (XL), music/retrowave (Original) as adaptation shapes. +### Other MCP clients -The skill lives at `streamdeck_assets/skill/streamdeck-designer/` in the repo; the `SKILL.md` body is ≤500 lines and `references/` are loaded on demand. +Clients that don't load Claude Code skills (Claude Desktop, Cursor, Codex, …) get a condensed mirror via the **`design_streamdeck_deck`** MCP prompt. Most clients expose it as a slash command or prompt picker — invoke it before describing the deck you want, and pass the user's intent via the `intent` argument if your client supports it. -## Default Tools +## Tools | Tool | What it does | |------|---------------| | `streamdeck_read_profiles` | Lists desktop profiles and page directories from the active ProfilesV3 or ProfilesV2 store | | `streamdeck_read_page` | Reads a page manifest and returns simplified button details plus the raw manifest | | `streamdeck_write_page` | Creates a new page or rewrites an existing page manifest | -| `streamdeck_create_icon` | Generates a PNG icon from a Material Design Icons name (e.g. `mdi:cpu-64-bit`) or from text (but not both). `shape="button"` (72x72, default) for keypad keys and encoder dial faces; `shape="touchstrip"` (200x100) for Stream Deck + / + XL dial segment backgrounds. ~7400 MDI icons are bundled offline; unknown names return close-match suggestions | +| `streamdeck_create_icon` | Generates a PNG icon from a Material Design Icons name (e.g. `mdi:cpu-64-bit`) or from text — provide one or the other, not both. `shape="button"` (72×72, default) for keypad keys and dial faces; `shape="touchstrip"` (200×100) for + / + XL touch strip backgrounds. ~7,400 MDI icons are bundled offline; unknown names return close-match suggestions | | `streamdeck_create_action` | Creates an executable shell script in `~/StreamDeckScripts/` and returns an Open action block | -| `streamdeck_restart_app` | Restarts the macOS Stream Deck desktop app after profile changes | -| `streamdeck_install_mcp_plugin` | Installs the bundled streamdeck-mcp Stream Deck plugin into the user's Elgato Plugins directory. `streamdeck_write_page` auto-installs it when an encoder button needs it, so direct use is rarely necessary | +| `streamdeck_restart_app` | Restarts the Stream Deck desktop app after profile changes (macOS only — raises on other platforms) | +| `streamdeck_install_mcp_plugin` | Installs the bundled streamdeck-mcp Stream Deck plugin into the Elgato Plugins directory. `streamdeck_write_page` auto-installs it the first time an encoder needs it, so direct use is rarely required | -## How the Profile Writer Works +## Editing workflow -- `ProfilesV3` is preferred when it exists because page UUIDs map cleanly to directories. -- `ProfilesV2` is still supported, but existing pages should be targeted by `directory_id` or `page_index` because Elgato stores opaque page directory names there. -- `streamdeck_write_page` can accept raw native action objects, or use convenience fields like `path`, `action_type`, `plugin_uuid`, and `action_uuid`. -- Generated icons are stored in `~/.streamdeck-mcp/generated-icons/`. -- Generated shell scripts are stored in `~/StreamDeckScripts/`. -- The bundled streamdeck-mcp Stream Deck plugin is installed into the Stream Deck Plugins directory (e.g., `~/Library/Application Support/com.elgato.StreamDeck/Plugins/` on macOS, `%APPDATA%\Elgato\StreamDeck\Plugins\` on Windows) once installed. It's a minimal shell whose only job is to declare encoder support so per-instance `Encoder.Icon` / `Encoder.background` writes survive an Elgato app restart. `streamdeck_write_page` installs it automatically the first time an encoder button needs it. +The Elgato desktop app keeps every profile in memory and rewrites the on-disk manifests from that snapshot when it quits — so any edit made while the app is running is wiped the next time it closes. The profile writer enforces a quit → write → relaunch cycle: -## Editing Workflow (Important) +1. Ensure the Elgato app is not running, or pass `auto_quit_app: true` to `streamdeck_write_page` to have it quit the app for you (AppleScript first, `killall` fallback). +2. Make as many `streamdeck_write_page` calls as you need — the app stays quit between them. +3. Call `streamdeck_restart_app` when you're done. The device re-reads the manifests on launch and your changes appear. -The Elgato desktop app keeps every profile in memory and rewrites the on-disk manifests from that snapshot when it quits, so any edit made while the app is running is wiped the next time it closes. The profile writer enforces a quit → write → relaunch cycle: +`streamdeck_write_page` raises `StreamDeckAppRunningError` when the app is running and `auto_quit_app` isn't set, so you can't accidentally write changes that get silently discarded. -1. Ensure the Elgato app is not running, or pass `auto_quit_app: true` to `streamdeck_write_page` to have it quit the app for you (AppleScript first, `killall` fallback). -2. Make as many `streamdeck_write_page` calls as you need — the app stays quit across them. -3. Call `streamdeck_restart_app` when you are done. The device re-reads the manifests on launch and your changes appear. +**Platform support.** The running-app guard, `auto_quit_app`, and `streamdeck_restart_app` are macOS-only. On Windows the Elgato app still clobbers manifests on close, so you'll need to quit it manually before authoring and relaunch it after — `auto_quit_app: true` is silently a no-op on non-macOS, and `streamdeck_restart_app` errors. -`streamdeck_write_page` raises a `StreamDeckAppRunningError` when the app is running and `auto_quit_app` is not set, so you cannot accidentally write changes that will be silently discarded. +If your Elgato app lives somewhere other than `/Applications/Elgato Stream Deck.app`, set `STREAMDECK_APP_PATH` to the bundle path. -If your Elgato app is installed somewhere other than `/Applications/Elgato Stream Deck.app`, set `STREAMDECK_APP_PATH` to the bundle path. +## Under the hood -## Usage Notes +- **`ProfilesV3` is preferred** when present (page UUIDs map cleanly to directories). `ProfilesV2` is still supported, but existing pages should be targeted by `directory_id` or `page_index` because Elgato uses opaque directory names there. +- **Native action objects** are accepted directly by `streamdeck_write_page`, alongside convenience fields like `path`, `action_type`, `plugin_uuid`, and `action_uuid`. +- **Generated icons** live in `~/.streamdeck-mcp/generated-icons/`. **Generated shell scripts** live in `~/StreamDeckScripts/`. +- **The bundled streamdeck-mcp plugin** is installed into the Stream Deck Plugins directory (e.g., `~/Library/Application Support/com.elgato.StreamDeck/Plugins/` on macOS, `%APPDATA%\Elgato\StreamDeck\Plugins\` on Windows). It's a minimal shell whose only job is to declare encoder support so per-instance `Encoder.Icon` / `Encoder.background` writes survive an Elgato app restart. -- `streamdeck_create_action` is the safest way to build shell-command buttons because it writes a standalone script and returns the native Open action block for it. -- The profile writer does not require exclusive USB access. +## Legacy USB mode -## Legacy USB Tools +The original USB-direct server is preserved for backwards compatibility — useful when you'd rather have the MCP server own the hardware directly (Linux, headless setups, or environments where the Elgato app isn't running). It exposes a different tool surface focused on direct hardware control: -The original USB-direct server is preserved for backwards compatibility. It still provides: +`streamdeck_connect`, `streamdeck_info`, `streamdeck_set_button`, `streamdeck_set_buttons`, `streamdeck_clear_button`, `streamdeck_get_button`, `streamdeck_clear_all`, `streamdeck_set_brightness`, `streamdeck_create_page`, `streamdeck_switch_page`, `streamdeck_list_pages`, `streamdeck_delete_page`, `streamdeck_disconnect`. -- `streamdeck_connect` -- `streamdeck_info` -- `streamdeck_set_button` -- `streamdeck_set_buttons` -- `streamdeck_clear_button` -- `streamdeck_get_button` -- `streamdeck_clear_all` -- `streamdeck_set_brightness` -- `streamdeck_create_page` -- `streamdeck_switch_page` -- `streamdeck_list_pages` -- `streamdeck_delete_page` -- `streamdeck_disconnect` +Run via: + +```bash +uvx --from streamdeck-mcp streamdeck-mcp-usb +``` -Use that mode only when you want the MCP server to own the hardware directly and the Elgato desktop app is not running. +In a client config, keep `"command": "uvx"` and use `"args": ["--from", "streamdeck-mcp", "streamdeck-mcp-usb"]`. ## Development ```bash -uv venv -uv pip install -e ".[dev]" +git clone https://github.com/verygoodplugins/streamdeck-mcp.git +cd streamdeck-mcp +uv venv && uv pip install -e ".[dev]" uv run pytest tests/ -v uv run ruff check . ``` diff --git a/docs/screenshots/slack-control-board.jpg b/docs/screenshots/slack-control-board.jpg new file mode 100644 index 0000000..16ced70 Binary files /dev/null and b/docs/screenshots/slack-control-board.jpg differ