lgtv-control-mcp
# lgtv-control-mcp
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server for controlling **LG webOS TVs** from AI clients like **Claude**, **Codex**, **GitHub Copilot**, and **Antigravity**. Works with one TV or several: register each TV under a name like "Office" and target it directly, or let commands fall through to a default TV.
Communication uses the webOS **SSAP** protocol over WebSocket, the same one used by the LG ThinQ app and projects like ColorControl.
## Tools
- `add_tv` / `remove_tv` / `list_tvs` / `set_default_tv`: manage your TVs by name (see [Multiple TVs](#multiple-tvs))
- `set_backlight`: backlight 0-100
- `set_peak_brightness`: peak brightness `off` / `low` / `medium` / `high`
- `set_picture_setting` / `set_picture_mode` / `get_picture_settings`: contrast, brightness, color, and picture mode
- `set_volume` / `volume_up` / `volume_down` / `get_volume` / `set_mute`
- `list_inputs` / `set_input`: switch HDMI and other inputs
- `list_apps` / `launch_app` / `close_app` / `get_foreground_app`
- `channel_up` / `channel_down` / `set_channel` / `list_channels` / `get_current_channel`
- `media_play` / `media_pause` / `media_stop` / `media_rewind` / `media_fast_forward`
- `power_off` / `screen_off` / `screen_on` / `get_power_state`
- `set_3d_on` / `set_3d_off`
- `show_toast`: display a notification on the TV
- `send_button`: press a remote button. Compatible names include `0`-`9`, `HOME`, `BACK`, `ENTER`, `EXIT`, `UP`, `DOWN`, `LEFT`, `RIGHT`, `RED`, `GREEN`, `YELLOW`, `BLUE`, `POWER`, `VOLUMEUP`, `VOLUMEDOWN`, `MUTE`, `MENU`, `CC`, `DASH`, `CHANNELUP`, `CHANNELDOWN`, `LIST`, `AD`, `SAP`, `PROGRAM`, `PLAY`, `PAUSE`, `STOP`, `REWIND`, `FASTFORWARD`, `GUIDE`, `AMAZON`, `NETFLIX`, `MAGNIFIER_ZOOM`, `LIVE_ZOOM`, `3D_MODE`, `ASPECT_RATIO`, `RECENT`, `RECORD`, `SCREEN_REMOTE`, `MYAPPS`
- `get_system_info` / `get_software_info` / `get_service_list`
- `ssap_request`: send any raw SSAP command (escape hatch)
Every control tool accepts an optional `tv` argument with the name of the target TV. Without it, the command goes to the default TV.
## Requirements
- **Node.js 18+**
- An LG webOS TV powered on and on the same local network
- The TV's **IP address** (Settings > Network)
- Mobile TV On enabled (Settings > Connection > External Devices > On via Wi-Fi)
## Installation
### Global install
```bash
npm install -g lgtv-control-mcp
```
This puts the `lgtv-control-mcp` binary in your PATH. Use `command: "lgtv-control-mcp"` with no `args` in your client config.
### Via npx (no install)
```bash
npx -y lgtv-control-mcp@latest
```
This does not install the package. The MCP client runs this command each time it starts the server; npm caches the package after the first download.
### From source
```bash
git clone https://github.com/brunofgmag/lgtv-control-mcp.git
cd lgtv-control-mcp
npm install
npm run build
node dist/index.js
```
## Pairing
The first time you connect, the TV shows a **"Connection Request"** prompt on screen. Accept it with the remote.
The client key is saved automatically to `~/.lgtv-control-mcp/keys.json` and reused on later connections. You don't need to configure it manually. If you want to pin a specific key, copy the value from that file into `LGTV_CLIENT_KEY`.
## Multiple TVs
You can register any number of TVs by name, without touching config files. Just ask your AI:
- "Add the TV 'Office' with IP 192.168.1.13"
- "Add the TV 'Living Room' with IP 192.168.1.20"
- "Which TVs do I have configured?"
- "Remove the Bedroom TV"
Commands that don't name a TV go to the **default TV**. The first TV you add becomes the default; to change it, say something like "make the Living Room TV the default". To control a specific TV, name it in the request: "pause the Office TV" or "set the volume to 15 on the Living Room TV".
A few details worth knowing:
- Adding a TV with a name that already exists updates it, so "change the Office TV's IP to 192.168.1.40" works without removing it first.
- Names are matched ignoring case; "office" and "Office" are the same TV.
- Each TV pairs separately. The first command sent to a new TV shows the connection prompt on that screen; accept it with that TV's remote.
- The list is stored in `~/.lgtv-control-mcp/tvs.json`, so it survives restarts and is shared by all your MCP clients.
If you only use one TV, you can skip all of this and set `LGTV_HOST` instead: when no TVs are registered, commands go to that address.
## Client configuration
`LGTV_HOST` is **optional** in every example below. It gives you a working single-TV setup right away, but you can leave the `env` block out entirely and register your TVs by chat afterwards, for example "Add the TV 'Office' with IP 192.168.1.13" (see [Multiple TVs](#multiple-tvs)). If both exist, registered TVs take precedence over `LGTV_HOST`.
### Claude Desktop
Open **Settings → Developer → Edit Config**. This opens `claude_desktop_config.json` directly in your editor. Add the entry and restart the app.
```json
{
"mcpServers": {
"lgtv-control": {
"command": "npx",
"args": ["-y", "lgtv-control-mcp@latest"],
"env": { "LGTV_HOST": "192.168.1.50" }
}
}
}
```
If you installed globally, use `"command": "lgtv-control-mcp"` with `"args": []`.
### Claude Code
The `--scope user` flag adds the server globally across all your projects:
```bash
claude mcp add lgtv-control --scope user --env LGTV_HOST=192.168.1.50 -- npx -y lgtv-control-mcp@latest
```
If you installed globally, replace `npx -y lgtv-control-mcp@latest` with just `lgtv-control-mcp`.
Or add it manually to `~/.claude/settings.json` (user-level, global):
```json
{
"mcpServers": {
"lgtv-control": {
"command": "npx",
"args": ["-y", "lgtv-control-mcp@latest"],
"env": { "LGTV_HOST": "192.168.1.50" }
}
}
}
```
### Codex CLI
Via CLI:
```bash
codex mcp add lgtv-control --env LGTV_HOST=192.168.1.50 -- npx -y lgtv-control-mcp@latest
```
Or in `~/.codex/config.toml`:
```toml
[mcp_servers."lgtv-control"]
command = "npx"
args = ["-y", "lgtv-control-mcp@latest"]
[mcp_servers."lgtv-control".env]
LGTV_HOST = "192.168.1.50"
```
### GitHub Copilot / VS Code
In `.vscode/mcp.json` or your user MCP settings:
```json
{
"servers": {
"lgtv-control": {
"type": "stdio",
"command": "npx",
"args": ["-y", "lgtv-control-mcp@latest"],
"env": { "LGTV_HOST": "192.168.1.50" }
}
}
}
```
For Copilot clients that expect the `mcpServers` format:
```json
{
"mcpServers": {
"lgtv-control": {
"type": "local",
"command": "npx",
"args": ["-y", "lgtv-control-mcp@latest"],
"env": { "LGTV_HOST": "192.168.1.50" },
"tools": ["*"]
}
}
}
```
### Antigravity
```json
{
"mcpServers": {
"lgtv-control": {
"command": "npx",
"args": ["-y", "lgtv-control-mcp@latest"],
"env": { "LGTV_HOST": "192.168.1.50" }
}
}
}
```
### Other MCP clients
Most clients that support local MCP via `stdio` use the same pattern:
```json
{
"mcpServers": {
"lgtv-control": {
"command": "npx",
"args": ["-y", "lgtv-control-mcp@latest"],
"env": { "LGTV_HOST": "192.168.1.50" }
}
}
}
```
## Environment variables
| Variable | Required | Default | Description |
| --- | --- | --- | --- |
| `LGTV_HOST` | no | - | TV IP address; used when no TVs are registered with `add_tv` |
| `LGTV_PORT` | no | `3001` | `3001` = wss (self-signed TLS); use `3000` for plain ws |
| `LGTV_CLIENT_KEY` | no | - | Pairing key; obtained and saved automatically on first use |
## Usage examples
Once configured, try asking your AI:
- "Set the TV backlight to 30"
- "Turn up the volume" / "Switch to HDMI 2"
- "Open Netflix" / "Turn off the TV screen"
- "Show a 'Dinner's ready' notification on the TV"
- "Add the TV 'Office' with IP 192.168.1.13"
- "Open YouTube on the Office TV"
- "Make the Office TV the default"
## Troubleshooting
- **"pairing timed out"**: accept the connection prompt on the TV screen and try again.
- **"could not connect"**: check the IP, that the TV is on and on the same network. Try `LGTV_PORT=3000`.
- **Backlight not changing**: some firmware versions require the luna path. The server already tries SSAP first and falls back automatically. Also check that your current picture mode allows manual backlight adjustment.
- **Command reached the wrong TV**: ask "which TVs do I have configured?" to see the registered TVs and the current default, then name the TV in the command or change the default.
- **Re-pairing**: delete the TV's entry in `~/.lgtv-control-mcp/keys.json` to force a new pairing prompt.
## How it works
The server opens an SSAP WebSocket with the TV, performs the registration handshake (with `pairingType: PROMPT`), and sends `ssap://...` requests. Remote control buttons use a separate pointer socket (`getPointerInputSocket`). Picture adjustments use `setSystemSettings`; when the direct SSAP path is rejected by the firmware, the server uses the luna trick (creating and closing an alert whose `onclose` points to `luna://com.webos.settingsservice/setSystemSettings`).
## License
[MIT](LICENSE)
TDQS
Scored across 37 tools
Several tools have overlapping functionality: send_button duplicates many specific control tools (volume_up/down, channel_up/down, set_mute, etc.), and set_backlight is a subset of set_picture_setting. This creates ambiguity for an agent selecting the appropriate tool.
Tool names follow a consistent verb_noun pattern using snake_case, with get_, set_, list_, media_ prefixes. Minor inconsistency: get_picture_settings vs set_picture_setting (singular vs plural), but overall highly predictable.
37 tools is excessive for a TV control server. Many tools (e.g., send_button, volume_up/down, set_volume) provide redundant ways to achieve the same result, bloating the surface and making it harder for agents to navigate.
The server lacks a tool to turn the TV on (only power_off exists) and does not provide a way to get the current active input. These are significant gaps for a TV control interface.