Skip to main content
Glama

streamdeck-mcp

An MCP server that lets AI agents read and write Elgato Stream Deck desktop profiles — the plain-JSON manifests the Elgato Stream Deck app uses — plus a small toolkit for generating icons, wrapping shell commands in script-backed keys, and restarting the app so changes take effect.

Fork of verygoodplugins/streamdeck-mcp (MIT, Jack Arturo / Very Good Plugins), extended with:

  • http_service.py — the same tool surface over MCP Streamable HTTP, behind a bearer-token gate, for agents that run on a different machine (the stdio server assumes its client runs where the deck is).

  • deck_status — a one-call health verdict for remote agents.

  • examples/ — a launchd service template, an env template, and a read-only end-to-end smoke client.

Ask an agent to "put a key for restarting Docker on the top-left of my deck" and it edits those manifests — no hunting for settings in the app.

The one rule that bites

The Elgato desktop app keeps profiles in memory and rewrites the manifest files from memory when it quits. Edits made behind the app's back are clobbered at the next quit. The correct cycle is:

  1. stop the app (streamdeck_write_page refuses to write while the app is running; auto_quit_app=true stops it for you),

  2. make your manifest edits (one or more streamdeck_write_page calls),

  3. restart the app (streamdeck_restart_app) so it reads your layout.

Always back up the profiles directory before writing.

Related MCP server: WebMCP Script

Transports

Transport

When

How

stdio

the MCP client runs on the deck's machine

the streamdeck-mcp CLI is spawned by the client (Claude Desktop, Claude Code, any MCP host)

Streamable HTTP

the agent runs elsewhere and needs the deck over the network

streamdeck-mcp-service (or python http_service.py) behind a bearer token; default bind 127.0.0.1:18802

The HTTP service adds one tool on top of the stdio surface: deck_status.

Quick start — stdio

git clone <this repo>
python3.11 -m venv .venv
.venv/bin/pip install -e .

Then point any local MCP host at .venv/bin/streamdeck-mcp (or at the upstream PyPI release if you don't need the HTTP service).

Quick start — HTTP service

git clone <this repo>
python3.11 -m venv .venv
.venv/bin/pip install -e .

# bearer token (keep it secret; chmod 600)
openssl rand -hex 24 > .token
chmod 600 .token

.venv/bin/streamdeck-mcp-service   # listens on 127.0.0.1:18802 by default

MCP client config (Streamable HTTP):

{
  "mcpServers": {
    "streamdeck": {
      "type": "http",
      "url": "http://127.0.0.1:18802/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

For a remote agent on your private/tailnet network, set DECK_MCP_BIND to a private interface address. The service enforces DNS-rebinding protection (it only accepts requests whose Host matches the bind address or loopback), so it is not internet-facing by default; put a TLS-terminating reverse proxy in front of it if the path between client and deck crosses an untrusted network.

Smoke-test a running service (read-only):

.venv/bin/python examples/test_client.py http://127.0.0.1:18802/mcp .token

On macOS, run it as a launchd service with examples/launchd.plist.tpl (the template comments cover install, stop, and log locations).

Environment

Variable

Default

Purpose

DECK_MCP_BIND

127.0.0.1:18802

host:port the HTTP service listens on

DECK_MCP_TOKEN_FILE

./.token

bearer token file; re-read per request, so rotation needs no restart

DECK_MCP_PROFILES_DIR

auto (ProfilesV3, then ProfilesV2, under the Elgato app's application-support dir)

override the profiles directory

DECK_MCP_SCRIPTS_DIR

~/StreamDeckScripts

where streamdeck_create_action writes scripts

DECK_MCP_ICONS_DIR

~/.streamdeck-mcp/generated-icons

where streamdeck_create_icon(s) writes PNGs

DECK_MCP_LOG

— (warnings to stderr)

JSONL audit file, one line per tool call

Tools

Tool

What it does

streamdeck_find_actions

search installed plugins/actions by text, plugin, controller, or profile usage; each result carries a paste-ready native action object

streamdeck_read_profiles

list profiles (V3/V2), pages, current/default page, device model

streamdeck_read_page

read one page, button by button, each with its raw native action object

streamdeck_write_page

create or replace a page manifest; refuses while the app is running unless auto_quit_app=true

streamdeck_create_icon

render one PNG icon from a Material Design Icons glyph and/or text

streamdeck_create_icons

batch-render icons

streamdeck_create_action

wrap a shell command in a script-backed Open action

streamdeck_read_plugins

installed plugins with action/settings schemas (for synthesizing actions)

streamdeck_restart_app

stop + relaunch the Elgato app (macOS) to pick up manifest edits

streamdeck_install_mcp_plugin

install the bundled in-app .sdPlugin

deck_status (HTTP only)

app running? profile inventory? uptime? last tool call?

Authoring a key, in preference order

  1. Paste a native action — copy the action object from streamdeck_find_actions, or a streamdeck_read_page button's raw field, into streamdeck_write_page. Best for installed plugins whose settings schema is opaque.

  2. Synthesize — from streamdeck_read_plugins: plugin_uuid + action_uuid + settings.

  3. Script-backed — streamdeck_create_action writes a shell script and returns an Open action; the key then runs your command.

Security model

  • The HTTP endpoint is bearer-token gated; the token file is re-read on every request and compared in constant time, so rotation needs no restart.

  • GET /health is intentionally unauthenticated and returns no profile data (liveness only).

  • DNS-rebinding protection: only requests whose Host header matches the bind host or loopback are accepted; anything else is dropped fail-closed (the connection is closed without a response).

  • The service is not meant to be exposed to the public internet; keep it on a private/tailnet interface, behind TLS if the path is untrusted. No telemetry, no outbound connections.

Troubleshooting

Symptom

Meaning / fix

⚠️ ... app is running ...

the Elgato app holds the manifests in memory; close it (or pass auto_quit_app=true), write, then streamdeck_restart_app

❌ ... profile not found

check DECK_MCP_PROFILES_DIR / that the profiles directory exists on the deck's machine

connection closed with no response

the client's Host header is not the bind address or loopback (DNS-rebinding protection) — point the client at the actual bind host:port, not a different alias

keys look stale after a write

the app rewrote the manifests from memory on quit; redo the cycle with the app stopped

Development

python3.11 -m venv .venv
.venv/bin/pip install -e .[dev]
.venv/bin/pytest            # profile engine, stdio server, HTTP service
.venv/bin/ruff check .

Tests run on any platform: the profile engine is pure file manipulation and the Stream Deck app is stubbed out.

Layout

profile_manager.py   the profile engine (V3/V2 manifests, pages, actions,
                     icons, app control)
profile_server.py    stdio MCP server (9 tools)
server.py            legacy USB/streamdeck-python device server (optional)
http_service.py      Streamable HTTP service (10 tools, bearer-token gated)
install_skill.py     installs the bundled streamdeck-designer skill
streamdeck_assets/   Material Design Icons (Apache-2.0) + designer skill
streamdeck_plugin/   bundled in-app .sdPlugin
tests/               pytest suite (synthetic profile fixtures)
examples/            launchd template, env template, smoke client

Attribution

Forked from verygoodplugins/streamdeck-mcp (MIT, © Jack Arturo / Very Good Plugins). The HTTP service, deck_status, the examples and the fork packaging are © Hugo Soares. The bundled Material Design Icons font is Apache-2.0 (see streamdeck_assets/MDI-LICENSE).

Available Tools

9 tools
streamdeck_create_actionA

Create an executable shell script in ~/StreamDeckScripts and return a native Open action block for it.

Inputs: name (required), command (required), filename, working_directory.

streamdeck_create_iconA

Generate one or many PNG icons. Button icons are 72x72 px; touchstrip segment icons are 200x100 px (use shape='touchstrip'). For a single icon: pass 'icon' (a Material Design Icons name like 'mdi:cpu-64-bit') OR 'text' (mutually exclusive with 'icon' — titles go on streamdeck_write_page's 'title' field since Elgato overlays them on images). For a full deck (often 30+ icons): pass 'icons' as a list of spec dicts to generate them all in one call and avoid the round-trip timeouts serial calls hit. ~7400 MDI icons bundled offline; unknown names return close-match suggestions. Returns either a single {path, size, ...} dict or {"icons": [...]} when 'icons' is used (each list element is a per-icon result or an {"error"} entry for that spec).

Inputs: icon, text, icons, shape, +7 more.

streamdeck_find_actionsA

Search configured buttons and dials across Stream Deck profiles/pages and return paste-ready native action objects for streamdeck_write_page. Prefer this FIRST when building layouts that reuse installed plugins (Home Assistant, Hue, OBS, Spotify, Wave Link, etc.): each hit includes a 'button' object ready for write_page (controller + key + action) so encoder/dial hits stay on the Encoder grid. You can also pass hit.action with controller=hit.controller yourself. Filters: query (substring), plugin_uuid, action_uuid, plugin_name, controller (keypad|encoder), optional profile scope. Returns a fresh ActionID per hit so multi-copy pastes are safe. For protected Elgato manifests that streamdeck_read_plugins cannot enumerate, this is the reliable path — configure once in the Stream Deck app, then find and reuse.

Inputs: limit, query, controller, profile_id, +4 more.

streamdeck_install_mcp_pluginA

Install the bundled streamdeck-mcp Stream Deck plugin into the user's Elgato Plugins directory. The plugin is a minimal shell that declares encoder support so that per-instance touchstrip icons and backgrounds written by streamdeck_write_page survive an Elgato app restart. Idempotent — returns installed=false when already present unless force=true. streamdeck_write_page also auto-installs this plugin when an encoder button targets it, so most callers do not need to invoke this directly.

Inputs: force.

streamdeck_read_pageA

Read a profile page by profile name or ID and page index or directory ID. Each button/dial includes a 'raw' native action object — copy it into streamdeck_write_page's button 'action' field to reuse configured plugin settings without guessing. For searching across many pages, prefer streamdeck_find_actions.

Inputs: page_index, profile_id, directory_id, profile_name.

streamdeck_read_pluginsA

List installed Stream Deck plugins and their declared actions from the user's Elgato Plugins directory, including each action's UUID, Name, States, and — best-effort — the Settings fields its Property Inspector expects (parsed from the action's PI HTML/JS). Use this to synthesize a NEW third-party action when streamdeck_find_actions finds no configured instance. Prefer find_actions (or read_page.raw) when the user already configured the button/dial in the Stream Deck app — especially for first-party Elgato plugins (OBS, Hue, Spotify, Zoom, …) that ship ELGATO-protected/binary manifests and are reported with parse_status instead of action metadata. settings_fields are heuristic: a missing field is not proof it is unused, and a listed field is not guaranteed required.

Inputs: plugin_id, include_raw_manifest, include_settings_schema.

streamdeck_read_profilesB

List Stream Deck desktop profiles from the active ProfilesV3 or ProfilesV2 directory.

Inputs: no named parameters.

streamdeck_restart_appA

Restart the macOS Stream Deck desktop app after profile changes.

Inputs: no named parameters.

streamdeck_write_pageA

Create a new page or replace/update an existing Stream Deck desktop page manifest. Authoring preference order: (1) paste a native 'action' from streamdeck_find_actions or streamdeck_read_page's button.raw — best for installed plugins with private Settings; (2) synthesize via plugin_uuid + action_uuid + settings from streamdeck_read_plugins when no configured instance exists; (3) script-backed Open actions via path / streamdeck_create_action for custom automation; also supports page-nav (action_type) and the bundled MCP dial. Validation: unknown plugin_uuid/action_uuid raises a clear error listing what is installed; missing settings fields are reported non-fatally in the result's 'warnings'. IMPORTANT: the Elgato desktop app overwrites profile manifests from its in-memory state on quit, so writes made while the app is running are lost. This tool refuses to write when the app is running unless auto_quit_app=True is passed. Call streamdeck_restart_app once your edits are complete to make the changes visible on the device.

Inputs: buttons, page_name, create_new, page_index, +6 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.1/5.0

    Scored across 9 tools

    Disambiguation5/5

    Each tool has a clearly distinct primary purpose: profile/page reading, page writing, icon generation, action discovery/creation, plugin inspection/installation, and app restart. The potentially overlapping discovery tools (find_actions, read_plugins, read_page) are differentiated by whether they search configured instances, enumerate installed plugin declarations, or read a specific page, and the descriptions explicitly guide when to prefer each.

    Naming Consistency5/5

    All tool names use a consistent snake_case verb_noun pattern with a uniform streamdeck_ prefix. The verbs are predictable and readable, with no mixing of camelCase or vague generic names.

    Tool Count5/5

    Nine tools is well-scoped for a Stream Deck automation server, covering reading, writing, asset creation, action discovery, plugin setup, and app lifecycle. Each tool appears to earn its place without obvious redundancy.

    Completeness4/5

    The surface covers the main workflow: list/read profiles, read/write pages, find/create actions, generate icons, inspect plugins, and restart/install the helper plugin. Minor gaps remain around explicit page listing/deletion or profile lifecycle management, but core authoring and discovery workflows are supported.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      B
      maintenance
      Enables an AI agent to take full control of a machine — running shell commands, editing files, managing git, packages and processes, and reaching the network — with batched execution, server-side variables and selectable tool profiles that keep token usage low. It also speaks HTTP so the same agent can operate a remote build box, NAS or VPS.
      26
      1
      MIT
    • F
      license
      Not graded
      quality
      A
      maintenance
      Exposes a governed suite of 38 tools over a single OAuth-gated MCP endpoint, letting web-based AI clients such as Claude, ChatGPT, and Grok read and edit files, run whitelisted shell commands, search, query SQLite, use Git, automate browsers and DevTools, manage background tasks, reach localhost services, and control the clipboard, notifications, ports, and Postman. This turns remote AI chats into secure operators of a local machine without granting unrestricted shell access.
      -