streamdeck-mcp
Enables AI agents to read and write Elgato Stream Deck desktop profiles, including managing pages, actions, and icons, wrapping shell commands as script-backed keys, and restarting the Stream Deck app to apply manifest changes.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@streamdeck-mcp add a key for restarting Docker to the top-left of my deck"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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:
stop the app (
streamdeck_write_pagerefuses to write while the app is running;auto_quit_app=truestops it for you),make your manifest edits (one or more
streamdeck_write_pagecalls),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 |
Streamable HTTP | the agent runs elsewhere and needs the deck over the network |
|
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 defaultMCP 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 .tokenOn 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 |
|
|
|
|
| bearer token file; re-read per request, so rotation needs no restart |
| auto ( | override the profiles directory |
|
| where |
|
| where |
| — (warnings to stderr) | JSONL audit file, one line per tool call |
Tools
Tool | What it does |
| search installed plugins/actions by text, plugin, controller, or profile usage; each result carries a paste-ready native |
| list profiles (V3/V2), pages, current/default page, device model |
| read one page, button by button, each with its raw native action object |
| create or replace a page manifest; refuses while the app is running unless |
| render one PNG icon from a Material Design Icons glyph and/or text |
| batch-render icons |
| wrap a shell command in a script-backed |
| installed plugins with action/settings schemas (for synthesizing actions) |
| stop + relaunch the Elgato app (macOS) to pick up manifest edits |
| install the bundled in-app |
| app running? profile inventory? uptime? last tool call? |
Authoring a key, in preference order
Paste a native action — copy the
actionobject fromstreamdeck_find_actions, or astreamdeck_read_pagebutton'srawfield, intostreamdeck_write_page. Best for installed plugins whose settings schema is opaque.Synthesize — from
streamdeck_read_plugins:plugin_uuid+action_uuid+settings.Script-backed —
streamdeck_create_actionwrites a shell script and returns anOpenaction; 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 /healthis intentionally unauthenticated and returns no profile data (liveness only).DNS-rebinding protection: only requests whose
Hostheader 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 |
| the Elgato app holds the manifests in memory; close it (or pass |
| check |
connection closed with no response | the client's |
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 clientAttribution
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 toolsstreamdeck_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
Scored across 9 tools
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.
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.
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.
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
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Persistent memory, hybrid search and a goal graph for AI agents, over stdio or remote HTTP.
Build, validate, deploy — HTTP APIs, cron jobs, webhooks and MCP tools — from your AI client.
Remote streamable-HTTP MCP server running on a single Cloudflare Worker. Your assistant gets live Airbnb, Amazon, Booking.com, Google Flights, Maps and Reddit data, social search on X, Instagram and TikTok, the Meta Ad Library, and image/video generation without any keys. Connect your own accounts to let it send WhatsApp or Telegram messages, work an IMAP inbox, manage Meta Ads campaigns and publish to X and LinkedIn. OAuth 2.1 with PKCE; stored credentials are AES-256-GCM encrypted.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to remotely search, update, manage files, and run terminal commands on a desktop environment via Streamable HTTP with OAuth 2.0, supporting ChatGPT and web agent integrations.1,287,096 npm1MIT
- AlicenseBqualityBmaintenanceEnables AI clients to connect locally over stdio, discover and read schemas for native page tools, and execute user-authorized operations supplied by websites and independent user scripts.13MIT
- AlicenseAqualityBmaintenanceEnables 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.261MIT
- FlicenseNot gradedqualityAmaintenanceExposes 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.-