---
title: MCP server
description: Connect agents to System Bridge over the Model Context Protocol to query system data and trigger actions.
---

System Bridge includes a <a href="https://modelcontextprotocol.io" target="_blank" rel="noopener noreferrer">Model Context Protocol</a> (MCP) server, letting agents and other MCP clients query your system and trigger actions.

The primary endpoint uses Streamable HTTP:

```text
http://{host}:9170/api/mcp
```

Replace `{host}` with the hostname or IP address of the machine running System Bridge. The port can be changed with the `SYSTEM_BRIDGE_PORT` environment variable. Use `https://` when System Bridge is exposed through SSL configuration or TLS termination.

All connections require your API token. Pass it as a `token` query parameter:

```text
http://{host}:9170/api/mcp?token=YOUR_TOKEN
```

Clients that support custom headers can instead send:

```text
Authorization: Bearer YOUR_TOKEN
```

A missing or invalid token is rejected with `401 Unauthorized`. See [how to find your token](/using/cli/#token).

:::caution
The token provides access to MCP tools that can read system data, send notifications, control media, displays and windows, and run the commands in your allowlist. Treat token-bearing URLs as secrets, and use HTTPS when the connection crosses an untrusted network.
:::

## Home Assistant

Home Assistant's <a href="https://www.home-assistant.io/integrations/mcp" target="_blank" rel="noopener noreferrer">Model Context Protocol integration</a> can connect directly to System Bridge. No command or WebSocket bridge is required.

1. In Home Assistant, go to **Settings** > **Devices & services**.
2. Select **Add integration**, then search for **Model Context Protocol**.
3. Enter the token-bearing Streamable HTTP URL, for example `http://192.168.1.10:9170/api/mcp?token=YOUR_TOKEN`.
4. Complete the setup flow.
5. Configure your conversation agent to use the System Bridge MCP tools.

Use a hostname or IP address that Home Assistant can reach. Use `https://` if the endpoint is exposed over HTTPS.

## Other MCP clients

For a remote MCP client, choose its Streamable HTTP transport and use the token-bearing endpoint:

```text
http://{host}:9170/api/mcp?token=YOUR_TOKEN
```

Client configuration formats and option names vary. Some clients accept a remote MCP URL directly, while others require a transport type, URL field, or custom `Authorization` header. Follow the client's documentation rather than assuming a particular configuration key.

## Legacy WebSocket transport

The SDK-backed WebSocket transport remains available at the same path for compatibility:

```text
ws://{host}:9170/api/mcp?token=YOUR_TOKEN
```

Use `wss://` when connecting through TLS. Streamable HTTP is the primary setup for clients that support remote MCP servers.

For a stdio-only client, <a href="https://github.com/vi/websocat" target="_blank" rel="noopener noreferrer">`websocat`</a> can bridge the client's standard input and output to the legacy WebSocket:

```bash
websocat "ws://localhost:9170/api/mcp?token=YOUR_TOKEN"
```

Configure the client to launch `websocat` with the WebSocket URL as its argument. The exact command and argument fields depend on the client.

## Results and Code Mode

Every tool declares an output schema and returns its result as structured content, with the same JSON as text for clients that only read text. Actions return `{ "success": true, "message": "..." }`. `system_bridge_list_commands` and `system_bridge_list_command_prefabs` wrap their lists in an object, as `commands` and `prefabs`, since structured content has to be an object.

Clients with a Code Mode, such as <a href="https://opencode.ai" target="_blank" rel="noopener noreferrer">OpenCode</a>, use the schemas to type each tool, so an agent can chain calls in one script rather than one tool call at a time. For example, with the server named `system-bridge`:

```js
const sb = tools["system-bridge"]
const { processes } = await sb.system_bridge_get_data({ modules: ["processes"] })
const top = processes.toSorted((a, b) => (b.cpu_usage ?? 0) - (a.cpu_usage ?? 0)).slice(0, 3)
await sb.system_bridge_send_notification({
  title: "Busiest processes",
  message: top.map((p) => `${p.name}: ${(p.cpu_usage ?? 0).toFixed(1)}%`).join("\n"),
})
```

## Tools

### `system_bridge_audio_control`

Set an audio output's, input's or app's volume or mute state, make an output or input the default, or move an app to another output. See [audio control](/api/websocket/control/#audio-control) for what each platform supports.

- `id` (required): An `id` from the `audio` module's `outputs`, `inputs` or `apps`.
- `volume` (optional): Volume in percent, 0-100.
- `muted` (optional): `true` to mute it, `false` to unmute it.
- `default` (optional): `true` to make it the default output or input.
- `output` (optional): An output's `id`, to move an app's sound to it. Linux only.

Example arguments:

```json
{ "id": "alsa_output.pci-0000_00_1f.3.analog-stereo", "volume": 40 }
```

### `system_bridge_bluetooth_connect`

Connect a paired Bluetooth device. See [Bluetooth control](/api/websocket/control/#bluetooth-control) for what each platform supports.

- `address` (required): The device's `address` from the `bluetooth` module's `devices`.

### `system_bridge_bluetooth_disconnect`

Disconnect a paired Bluetooth device.

- `address` (required): The device's `address` from the `bluetooth` module's `devices`.

### `system_bridge_bluetooth_forget`

Forget a paired Bluetooth device, unpairing it. It has to be paired again before it can connect.

- `address` (required): The device's `address` from the `bluetooth` module's `devices`.

### `system_bridge_bluetooth_set_power`

Turn Bluetooth on or off.

- `powered` (required): `true` to turn it on, `false` to turn it off.

### `system_bridge_camera_capture`

Take a picture with a webcam, returned as an image and as its base64 JPEG. Needs the `cameras.capture` setting on. See [camera capture](/api/websocket/control/#camera-capture). Linux only.

- `id` (required): The camera's `id` from the `cameras` module.
- `width` and `height`: One of the camera's `sizes` to take it at, instead of its current size.

### `system_bridge_camera_control`

Set one of a webcam's controls, such as its brightness or zoom. See [camera control](/api/websocket/control/#camera-control). Linux only.

- `id` (required): The camera's `id` from the `cameras` module.
- `control` (required): The control's `id` from that camera's `controls`.
- `value` (required): A whole number within the control's range. A boolean's is `0` or `1`, and a menu's is one of its options' `value`.

### `system_bridge_desktop_close_window`

Ask a window to close, as its close button does. See [desktop control](/api/websocket/control/#desktop-control).

- `id` (required): The window's `id` from the `desktop` module's `windows`.

### `system_bridge_desktop_focus_window`

Focus a window, switching to its workspace.

- `id` (required): The window's `id` from the `desktop` module's `windows`.

### `system_bridge_desktop_focus_workspace`

Show and focus a workspace. Not available on Windows.

- `id` (required): The workspace's `id` from the `desktop` module's `workspaces`.

### `system_bridge_desktop_set_keyboard_layout`

Switch the keyboard to one of its layouts. Not available on Wayland desktops other than Hyprland.

- `layout` (required): One of the `desktop` module's `keyboard.layouts`.

### `system_bridge_desktop_set_window_state`

Make a window fullscreen, maximised or minimised, or not. Set any of them together.

- `id` (required): The window's `id` from the `desktop` module's `windows`.
- `fullscreen` (optional): `true` or `false`. Not available on Windows.
- `maximised` (optional): `true` or `false`.
- `minimised` (optional): `true` or `false`. Not available on Hyprland.

Example arguments:

```json
{ "id": "0x555dbf21c9e0", "fullscreen": true }
```

### `system_bridge_discord_control`

Control Discord, which must be [set up](/features/discord/) first. It takes the same actions and fields as [Discord control](/api/websocket/control/#discord-control).

- `action` (required): What to do, such as `JOIN_VOICE_CHANNEL`, `TOGGLE_MUTE` or `PLAY_SOUND`.
- `channel_id`, `user_id`, `sound_id`, `device_id`, `mode` and `value` (optional): What the action needs, with ids from the `discord` module.

Example arguments:

```json
{ "action": "TOGGLE_MUTE" }
```

### `system_bridge_display_control`

Set a display's brightness or power state. See [display control](/api/websocket/control/#display-control) for what each display supports.

- `id` (required): The display's `id` from the `displays` module.
- `brightness` (optional): Brightness in percent, 0-100.
- `power_state` (optional): `on`, `standby`, `suspend` or `off`.

Example arguments:

```json
{ "id": "HDMI-A-2", "brightness": 40 }
```

### `system_bridge_get_clipboard`

Read the text on the clipboard. It takes no arguments. Works on Linux, with wl-clipboard, xclip or xsel, on Windows and on macOS. See [clipboard](/api/websocket/input/#clipboard).

### `system_bridge_get_data`

Get current system information from one or more data modules. The tool returns each requested module's latest data as JSON.

- `modules` (required): An array of module names to fetch.

Available modules:

| Module | Description |
| --- | --- |
| `audio` | Output and input devices, their volume, mute state and which are the defaults. |
| `battery` | Charge level, charging state, and time remaining. |
| `bluetooth` | Paired Bluetooth devices and whether they're connected. |
| `cameras` | Webcams and their settings, such as brightness, zoom and focus. Linux only. |
| `cpu` | Usage, frequency, core counts, and per-core load. |
| `desktop` | Open windows, workspaces, the keyboard layout, on Hyprland its displays, input devices, binds and global shortcuts, and the Omarchy shell's menus and panels. |
| `disks` | Mounted devices, partitions, and used/free space. |
| `displays` | Connected displays, resolution, refresh rate, brightness and power state. |
| `gpus` | GPU model, load, memory, and temperature. |
| `media` | Currently playing media: title, artist, and playback state. |
| `memory` | Physical and virtual memory usage. |
| `networks` | Network interfaces, addresses, and throughput. |
| `processes` | Running processes with CPU and memory usage. |
| `sensors` | Hardware sensors such as temperatures and fan speeds. |
| `system` | Hostname, OS, uptime, users, and version details. |

Example arguments:

```json
{ "modules": ["cpu", "memory", "battery"] }
```

### `system_bridge_get_history`

Get the recent readings System Bridge keeps for its charts, the same as the [`GET_HISTORY`](/api/websocket/data/#getting-history) event. Each series is a list of `{ "time", "value" }` samples, with `time` in Unix milliseconds.

- `since` (optional): Only readings after this time, in Unix milliseconds. Omit it for the whole history.
- `modules` (optional): Only these modules' readings: `battery`, `cpu`, `disks`, `gpus`, `memory`, `networks`, `sensors` or `system`. Omit it for every module.

Example arguments:

```json
{ "since": 1792857600000, "modules": ["cpu", "memory"] }
```

### `system_bridge_hyprland_dispatch`

Run a dispatcher on the focused window on Hyprland, or another window after focusing it, as its bind would. See [Hyprland control](/api/websocket/control/#hyprland-control) for what each does.

- `dispatcher` (required): `center_window`, `cycle_next_window`, `cycle_previous_window`, `group_next`, `group_previous`, `lock_group`, `lock_groups`, `move_focus`, `swap_window`, `toggle_group`, `toggle_split`, `unlock_group` or `unlock_groups`.
- `direction` (optional): `down`, `left`, `right` or `up`. Needed for `move_focus` and `swap_window`.
- `id` (optional): The `id` of a window from the `desktop` module's `windows` to focus first.

### `system_bridge_hyprland_move_window`

Move a window to another workspace on Hyprland, leaving the focus where it is. See [Hyprland control](/api/websocket/control/#hyprland-control).

- `id` (required): The window's `id` from the `desktop` module's `windows`.
- `workspace` (required): The workspace to move it to.

### `system_bridge_hyprland_move_workspace`

Move a workspace to another display on Hyprland.

- `id` (required): The workspace's `id` from the `desktop` module's `workspaces`.
- `display` (required): The display's `id` from the `displays` module.

### `system_bridge_hyprland_reload_config`

Reload Hyprland's config. It takes no arguments.

### `system_bridge_hyprland_rename_workspace`

Rename a workspace on Hyprland.

- `id` (required): The workspace's `id` from the `desktop` module's `workspaces`.
- `name` (optional): The new name. Leave it out to give the workspace back its default name.

### `system_bridge_hyprland_run_global_shortcut`

Trigger a global shortcut an app registered with Hyprland, such as one that opens a panel.

- `name` (required): A `name` from the `desktop` module's `hyprland.global_shortcuts`.

### `system_bridge_hyprland_set_option`

Set any Hyprland config option, such as turning animations off, until the config reloads. Read its type first with `system_bridge_hyprland_get_option`. See [options](/api/websocket/control/#options) for the values each type takes.

- `name` (required): The option, such as `animations.enabled` or `general:layout`.
- `value` (required): `true` or `false`, a number, text, or two numbers as `[5, 10]`, by the option's type. The options in the `desktop` module's `hyprland.options` only take a number of 0 or more.

### `system_bridge_hyprland_get_option`

Read any Hyprland config option: its type, its value, and whether the config sets it rather than leaving the default.

- `name` (required): The option, such as `general.col.active_border` or `general:layout`.

### `system_bridge_hyprland_get_cursor_position`

Get where the pointer is on Hyprland, in pixels across every display. It takes no arguments.

### `system_bridge_hyprland_move_cursor`

Move the pointer on Hyprland.

- `x` and `y` (required): Where to put it, in pixels across every display.

### `system_bridge_hyprland_get_log`

Get the end of Hyprland's log, oldest line first. It takes no arguments.

### `system_bridge_hyprland_send_event`

Send a custom Hyprland event, as a bind to the event dispatcher would. Listeners get it as a [Hyprland event](/api/websocket/data/#hyprland-events).

- `name` (required): The event's text.

### `system_bridge_hyprland_exec`

Run a command through Hyprland, in its environment. Needs the `desktop.hyprlandCode` setting on. See [running code](/api/websocket/control/#running-code).

- `command` (required): The command line, such as `kitty --class scratch`.

### `system_bridge_hyprland_dispatch_raw`

Run any Hyprland dispatch, written as the running config takes it: Lua when the `desktop` module's `hyprland.version.config_provider` is `lua`, otherwise classic. Needs the `desktop.hyprlandCode` setting on.

- `dispatch` (required): The dispatch, such as `hl.dsp.focus({ workspace = "2" })` or `workspace 2`.

### `system_bridge_hyprland_eval`

Run one line of Lua in Hyprland, with a Lua config. Hyprland only replies whether it ran. Needs the `desktop.hyprlandCode` setting on.

- `code` (required): The Lua.

### `system_bridge_hyprland_set_submap`

Switch Hyprland's binds to a submap, such as a resize mode, or back to the default.

- `name` (optional): A submap one of the `desktop` module's `hyprland.binds` is in. Leave it out for the default.

### `system_bridge_hyprland_set_window`

Float or tile a window on Hyprland, pin or unpin it, and move or resize it. Pinning a tiled window floats it first.

- `id` (required): The window's `id` from the `desktop` module's `windows`.
- `floating` (optional): `true` to float the window, `false` to tile it.
- `pinned` (optional): `true` to show it on every workspace, `false` to unpin it.
- `x` and `y` (optional, together): Where to put the window's top-left corner, in pixels across every display.
- `width` and `height` (optional, together): The window's size in pixels.

### `system_bridge_hyprland_kill_window`

Kill a window's app on Hyprland without asking it to close, so anything unsaved is lost. `system_bridge_desktop_close_window` asks it to close instead.

- `id` (required): The window's `id` from the `desktop` module's `windows`.

### `system_bridge_keyboard_keypress`

Press a key in whichever window has focus. See [keyboard keypress](/api/websocket/input/#keyboard-keypress).

- `key` (required): The key to press, such as `a`, `enter` or `space`.
- `modifiers` (optional): Keys to hold while pressing it: `shift`, `ctrl`, `alt` or `cmd`.
- `delay` (optional): Milliseconds to wait first.

### `system_bridge_keyboard_text`

Type text in whichever window has focus. See [keyboard text](/api/websocket/input/#keyboard-text).

- `text` (required): The text to type.
- `delay` (optional): Milliseconds to wait first.

### `system_bridge_kill_process`

End a process. See [process control](/api/websocket/control/#process-control).

- `id` (required): The process's `id` from the `processes` module.
- `force` (optional): `true` to end it straight away rather than asking it to stop, losing anything unsaved.

### `system_bridge_list_command_prefabs`

List the [prefab commands](/features/commands/#prefab-commands), with whether each can run on this system, what it would run, and why not. It takes no arguments. An assistant can't add them: a prefab only runs once you add it to your commands.

### `system_bridge_list_commands`

List the [commands](/features/commands/) you've allowed System Bridge to run, with each one's `id`, `name`, `command`, `arguments`, `workingDir` and `prefab`. It takes no arguments.

### `system_bridge_lock`

Lock the screen. It takes no arguments.

### `system_bridge_media_control`

Control playback of the system's current media session, or another player.

- `action` (required): The action to perform. Must be uppercase.
- `player`: A player's `id` from the `media` module's `players`. Leave it out for the player the module reports.
- `position`: Seconds from the start of the track, for `SEEK`.
- `volume`: 0-1, for `SET_VOLUME`.
- `shuffle`: `true` or `false`, for `SET_SHUFFLE`.
- `repeat`: `NONE`, `TRACK` or `LIST`, for `SET_REPEAT`.

Available actions:

| Action | Effect |
| --- | --- |
| `MUTE` | Toggle mute. |
| `NEXT` | Skip to the next track. |
| `PAUSE` | Pause playback. |
| `PLAY` | Resume playback. |
| `PREVIOUS` | Return to the previous track. |
| `STOP` | Stop playback. |
| `VOLUME_DOWN` | Lower the volume. |
| `VOLUME_UP` | Raise the volume. |
| `SEEK` | Move to `position`. |
| `SET_VOLUME` | Set the player's volume to `volume`. |
| `SET_SHUFFLE` | Turn shuffle on or off. |
| `SET_REPEAT` | Set what repeats. |

On Windows, only the actions up to `VOLUME_UP` work, and not with `player`.

Example arguments:

```json
{ "action": "PAUSE" }
```

```json
{ "action": "SEEK", "player": "vlc", "position": 90 }
```

### `system_bridge_omarchy_menu`

Open, close or toggle the Omarchy menu at one of its routes, while the Omarchy shell runs. See [Omarchy control](/api/websocket/control/#omarchy-control).

- `action` (required): `open`, `close` or `toggle`.
- `id` (optional): A menu `id` or one of its `aliases` from the `desktop` module's `omarchy.menus`, such as `root`. Not needed to close the menu.

### `system_bridge_omarchy_panel`

Open, close or toggle a panel or overlay of an Omarchy shell plugin.

- `action` (required): `open`, `close` or `toggle`.
- `id` (required): A panel `id` from the `desktop` module's `omarchy.panels`, such as `omarchy.audio`.

### `system_bridge_open`

Open a URL in the default browser, or a file or folder with its default app. See [open a path](/api/websocket/control/#open-a-path).

- `url` (optional): A URL to open.
- `path` (optional): A file or folder to open.

Give one of them.

### `system_bridge_run_command`

Run a command from the allowlist and wait for it to finish. It returns the command's `exitCode`, `stdout` and `stderr`, and `error` when it couldn't run or timed out. Commands time out after 5 minutes.

- `id` (required): The command's `id` from `system_bridge_list_commands`.

Example arguments:

```json
{ "id": "backup" }
```

Only commands in the allowlist can run. Power actions such as shutdown and restart aren't available as tools, so an assistant can't trigger them by mistake. Locking is, with `system_bridge_lock`, as it loses nothing.

### `system_bridge_send_notification`

Send a desktop notification to the machine running System Bridge.

- `title` (required): The notification title.
- `message` (required): The notification body text.
- `icon` (optional): An icon name to display with the notification.
- `urgency` (optional): `low`, `normal` or `critical`. Critical notifications stay until dismissed. Linux only.

Example arguments:

```json
{ "title": "Build finished", "message": "All tests passed", "icon": "dialog-information" }
```

### `system_bridge_set_clipboard`

Put text on the clipboard, ready to paste. Linux only, with wl-clipboard, xclip or xsel. See [clipboard](/api/websocket/input/#clipboard).

- `text` (required): The text to put on the clipboard.

### `system_bridge_set_power_profile`

Switch the power profile. Linux with power-profiles-daemon or tuned-ppd only. See [system control](/api/websocket/control/#system-control).

- `profile` (required): One of the `system` module's `power_profiles`, such as `power-saver`, `balanced` or `performance`.

### `system_bridge_set_keep_awake`

Keep the system from idling, so it doesn't lock, blank the screen or sleep for being left alone, or let it idle again. On Omarchy this is the bar's stay awake switch. Linux only.

- `enabled` (required): `true` to keep it awake, `false` to let it idle.
- `duration` (optional): Seconds until it may idle again.

## Troubleshooting

### Connection refused

- Confirm System Bridge is running. See [Running](/running/).
- Check the port (default `9170`) and `SYSTEM_BRIDGE_PORT` if you changed it.
- Verify the host in your config matches the machine running the backend.
- Confirm the client uses Streamable HTTP unless it explicitly needs the legacy WebSocket transport.

### Unauthorized (`401`)

- The token is missing or incorrect. Run `system-bridge client token` on the host running the backend.
- Pass the token as a `token` query parameter or `Authorization: Bearer {token}` header.

### Tool execution fails

- Check backend logs for the underlying error.
- For `system_bridge_get_data`, use valid module names (see the table above).
- For `system_bridge_media_control`, actions must be uppercase (`PLAY`, `PAUSE`, and so on).
- For `system_bridge_run_command`, the `id` must be in the allowlist. Get the ids from `system_bridge_list_commands`.
