samsung-tv-mcp
# samsung-tv-mcp
[](https://m8ven.ai/mcp/alvarengajr1-samsung-tv-mcp-1fx6sz?s=readme)
An [MCP](https://modelcontextprotocol.io) server that lets AI agents control a Samsung Tizen smart TV on your local
network: power, volume, remote keys, text input, apps and YouTube videos.
```text
You: put lo-fi beats on the living room TV, not too loud
Claude: tv_status → standby
tv_power {on: true}
tv_play_youtube {video: "jfKfPfyJRdk"}
tv_set_volume {level: 15}
```
It talks to the TV directly over its local APIs. No cloud account, no SmartThings, no hub.
## Tools
| Tool | What it does |
| --- | --- |
| `tv_status` | Power (`on` / `standby` / `off`), name, model, IP, paired, volume, mute. Finds the TV again if DHCP moved it. |
| `tv_power` | On (Wake-on-LAN, then the power key if it stays in standby) or off. Idempotent. |
| `tv_press_keys` | Remote keys in order, using aliases (`up`, `ok`, `back`, `home`, `play`, `0`–`9`…) or raw `KEY_*` codes. |
| `tv_set_volume` | Absolute volume over DLNA, refused above a configurable cap. |
| `tv_type_text` | Fills the text field open on screen (search boxes). |
| `tv_list_apps` | Installed apps with their Tizen IDs. |
| `tv_launch_app` | Opens an app by name (`"prime"`) or ID. Ambiguous or unknown names come back as errors that list the options. |
| `tv_play_youtube` | Opens YouTube on a video, from a URL (`watch`, `youtu.be`, `shorts`, `embed`) or an ID. |
| `tv_pair` | Shows the "Allow" pop-up on the TV and waits for it to be accepted. |
Errors are returned as tool results (`isError`) with a stable code (`not_paired`, `tv_off`, `bad_request`) and a
message the model can act on, rather than thrown, so the agent can recover by itself: pair, turn the TV on, or pick
another app. The server also sends MCP `instructions` describing that flow.
### Guardrails
An agent holding a TV remote should not be able to blast the volume at 3 a.m.
- `tv_set_volume` refuses levels above `SAMSUNG_TV_MAX_VOLUME` (default 50).
- `tv_press_keys` allows at most 5 `volume_up` per call, so the cap cannot be bypassed with keys, and at most 30 keys.
- `KEY_POWER` is not accepted as a key; power goes through `tv_power`, which checks the current state first, so
"turn it on" never turns an already-on TV off.
- `tv_status` and `tv_list_apps` are annotated `readOnlyHint`, so clients can auto-approve them and ask before the rest.
## Setup
Requires Node.js 20+ and a Samsung TV from 2016 or later on the same network.
```bash
git clone https://github.com/<you>/samsung-tv-mcp && cd samsung-tv-mcp
npm install
npx samsung-tv-mcp discover # scans your /24 and remembers the TV
npx samsung-tv-mcp pair # accept the pop-up on the TV with the physical remote
npx samsung-tv-mcp status
```
The IP, MAC and pairing token are stored in `~/.samsung-tv-mcp/`.
### Claude Code
```bash
claude mcp add samsung-tv -- node /path/to/samsung-tv-mcp/src/cli.mjs
```
### Claude Desktop and other MCP clients
```json
{
"mcpServers": {
"samsung-tv": {
"command": "node",
"args": ["/path/to/samsung-tv-mcp/src/cli.mjs"],
"env": { "SAMSUNG_TV_MAX_VOLUME": "40" }
}
}
}
```
### Configuration
| Variable | Default | |
| --- | --- | --- |
| `SAMSUNG_TV_HOST` | discovered | Fixed TV address; disables rescanning. |
| `SAMSUNG_TV_SUBNET` | from local interfaces | `/24` prefixes to scan, comma separated (`192.168.1`). |
| `SAMSUNG_TV_MAX_VOLUME` | `50` | Volume cap for `tv_set_volume`. |
| `SAMSUNG_TV_EXTRA_APPS` | | App IDs to probe besides the built-in catalog. |
| `SAMSUNG_TV_DATA_DIR` | `~/.samsung-tv-mcp` | Where the config and token live. |
## How it talks to the TV
| What | How |
| --- | --- |
| Keys and text | WebSocket `wss://TV:8002/api/v2/channels/samsung.remote.control` with a token from pairing |
| State, apps | REST on `http://TV:8001/api/v2/` |
| Volume and mute | UPnP/DLNA RenderingControl on port 9197, no token needed |
| YouTube video | DIAL `POST http://TV:8080/ws/apps/YouTube` with body `v=<id>` |
| Power on | Wake-on-LAN to the TV's MAC, then the power key if it is still in standby |
Things learned on a 2025 TV (UN43U8600F) that the older libraries and docs do not cover:
- **The app list request is ignored.** Newer TVs never answer `ed.installedApp.get` or `ed.apps.launch` over the
WebSocket. They still answer `GET /api/v2/applications/<id>` one ID at a time, so the server probes a catalog of
known IDs (`src/apps.mjs`) in parallel and keeps the ones the TV recognizes.
- **DIAL needs a YouTube `Origin`.** `POST /ws/apps/YouTube` answers 403 unless the request carries
`Origin: https://www.youtube.com`.
- **Volume over DLNA is absolute and token-free**, which beats sending `KEY_VOLUP` twenty times.
- **A TV that turns off does not close the WebSocket.** The server drops the channel whenever the REST API stops
answering, otherwise it would keep reporting "connected".
- **Standby swallows Wake-on-LAN** while the network stack is still up, so `tv_power` falls back to the power key.
- **There is no API to install apps.** None of the store IDs respond.
## Tests
```bash
npm test
```
The suite runs a fake TV (`test/fake-tv.mjs`) that serves the same REST, DLNA, DIAL and WebSocket endpoints on one
local port, including pairing approval, timeouts and revoked tokens. The MCP tests connect a real MCP client to the
server over an in-memory transport, so they cover the protocol layer, not just the TV client.
## Library use
```js
import { SamsungTV } from 'samsung-tv-mcp';
const tv = new SamsungTV({ dataDir: './data', host: '192.168.1.50' });
await tv.pair();
await tv.launchApp('3201907018807'); // Netflix
```
## License
MIT
TDQS
Scored across 9 tools
Each tool maps to a distinct action on the TV, and descriptions clarify boundary cases well (tv_play_youtube vs tv_launch_app, status vs pair). The main overlap is volume control: tv_press_keys accepts volume_up/volume_down/mute aliases that duplicate the purpose of tv_set_volume, which could cause misselection.
All tools share a consistent tv_ snake_case prefix with predictable verb_noun naming (tv_press_keys, tv_set_volume, tv_list_apps, tv_launch_app). Minor deviations: tv_status and tv_power are noun-only, and tv_pair is a bare verb, but the pattern remains readable.
Nine tools is well-scoped for a TV remote server, with one tool per core capability (status, power, keys, volume, text, apps, YouTube, pairing). No redundant or filler tools.
The surface covers power lifecycle, input, volume, text entry, app discovery/launch, YouTube deep-linking, and pairing, which is most of a TV remote's domain. Gaps exist: no absolute channel selection (only channel_up/down keys) and no input/source enumeration or app install, though many of these can be worked around via tv_press_keys.