Skip to main content
Glama
README.md
# 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

C2.9/5.0

Scored across 37 tools

Disambiguation2/5

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.

Naming Consistency4/5

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.

Tool Count2/5

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.

Completeness2/5

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.

Maintenance

ActivityStale
ResponsivenessNo issues