Skip to main content
Glama
nuxnik

Busy Bar MCP Server

by nuxnik
README.md
# Busy Bar MCP Server

A Python [MCP](https://modelcontextprotocol.io/) server written with [FastMCP](https://fastmcp.endpoints.com/) that wraps the **[busybar_python_sdk](https://github.com/nuxnik/busybar-python-sdk)** to communicate with a physical [Busy Bar](https://busy.app/) device over HTTP. The Busy Bar is a digital time-management display — this MCP server provides 32 tools for account retrieval, system information, time operations, BLE control, and input events, exposing its functionality through the standard Model Context Protocol so other tools and AI assistants can interact with it programmatically.

> **Status:** 32 MCP tools are fully implemented across account retrieval, system information, time operations, device state queries, BLE control, and input events, with a complete test suite. The project is ready to use — see the [What's Next](#whats-next) section below for planned work.

## Prerequisites

- **Python ≥ 3.12** — the project pins Python 3.12+ (see `.python-version`)
- **[uv](https://github.com/astral-sh/uv)** — dependency manager and virtual environment resolver

## Installation

Clone the repository and install dependencies:

```sh
uv sync
```

This creates a virtual environment and installs all packages declared in `pyproject.toml`, including `busybar-python-sdk` ≥ 27.5.1 from production PyPI.

## Configuration

The server supports configuration through either a `.env` file or environment variables. The same two variables are required in both cases:

| Variable            | Description                                                                                                                                                         | Example                 |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `BUSYBAR_API_TOKEN` | API auth token for authenticating with the Busy Bar device; transmitted to the device as the `X-API-Token` header (per the `busybar-python-sdk` 27.5.1 generated `ApiKeyAuth` scheme — see Authentication below) | `my-super-secret-token` |
| `BUSYBAR_BASE_URL`  | IP address or hostname of the Busy Bar device (a scheme-prefixed `http://…` / `https://…` value is also accepted)                                                   | `10.0.4.20`             |

### Using `.env`

Create a `.env` file in the project root:

```env
BUSYBAR_API_TOKEN=my-super-secret-token
BUSYBAR_BASE_URL=10.0.4.20
```

The server loads `.env` when it starts.

### Using environment variables

You can also configure the server through variables already present in the process environment:

```sh
export BUSYBAR_API_TOKEN=my-super-secret-token
export BUSYBAR_BASE_URL=10.0.4.20
```

Then start the server normally.

### Configuration precedence

Environment variables take precedence over values in `.env`. The server loads `.env` with `override=False`, so an environment variable that has already been exported is never replaced by the value in `.env`.

For example, if `.env` contains:

```env
BUSYBAR_BASE_URL=10.0.4.20
```

but the shell contains:

```sh
export BUSYBAR_BASE_URL=10.0.4.30
```

then `10.0.4.30` is used.

## Usage

Set `BUSYBAR_API_TOKEN` and `BUSYBAR_BASE_URL` through either `.env` or the environment, then start the MCP server:

```sh
# One-shot run via uvx (no local install required)
BUSYBAR_API_TOKEN=… BUSYBAR_BASE_URL=10.0.4.20 uvx busybar-mcp

# Or install locally and run the console script
uv sync
BUSYBAR_API_TOKEN=… BUSYBAR_BASE_URL=10.0.4.20 busybar-mcp
```

### Local development and device testing with MCP Inspector

For local development, the Inspector needs to launch the installed `busybar-mcp` console script from the project environment. This ensures it uses the project's installed dependencies and loads `.env` from the project root.

1. Install the project dependencies:

   ```sh
   uv sync
   ```

2. Create a `.env` file in the project root using the variables documented above. Do not commit this file; it contains your device token.

3. Start the Inspector in one terminal:

   ```sh
   npm exec --yes --package=@modelcontextprotocol/inspector -- mcp-inspector
   ```

   Leave that terminal running and open the local URL it prints.

4. In the Inspector, select **STDIO** and configure:

   - **Command:** the absolute path to this checkout's `.venv/bin/busybar-mcp`
   - **Arguments:** leave empty
   - **Working directory:** the absolute path to this repository's root directory

   Use the project's own virtual environment and working directory. The server loads `.env` automatically; you do not need to enter `BUSYBAR_API_TOKEN` or `BUSYBAR_BASE_URL` in the Inspector when `.env` is configured.

5. Click **Connect**, open **Tools**, and call a read-only device tool such as `get_transport` or `get_timezone` to verify connectivity. Tools that change device settings or send input events should only be used when you intend to make that change.

**Troubleshooting:** if `mcp dev` fails with `inspector: command not found`, use the manual Inspector workflow above. If the Inspector cannot connect, verify the command points to `.venv/bin/busybar-mcp`, the working directory is the project root, `.env` contains both required values, and the device is reachable from this machine.

`mcp_entry.py` is a dev-only entry helper for `mcp dev` / the MCP Inspector: it loads `.env` and registers tools on the server object, but is **not** a standalone server launcher. The regular `mcp dev` path may start a separate environment; for validating the locally installed SDK and dependencies, use the Inspector configuration above.

Once started, clients can connect to the server using their MCP transport.

## MCP Tools

### Account tools

| Tool                  | Description                                                                      |
| --------------------- | -------------------------------------------------------------------------------- |
| `get_account_info`    | Retrieve linked account information (email, account ID) from the Busy Bar device |
| `get_account_status`  | Check MQTT connection state for the linked account                               |
| `get_account_backend` | Inspect MQTT backend configuration (server URL, certificate settings)            |

### System tools

| Tool                | Description                                                           |
| ------------------- | --------------------------------------------------------------------- |
| `get_api_version`   | Query the API version supported by the device                         |
| `get_transport`     | Get the current network transport type (USB or Wi-Fi)                 |
| `get_device_status` | Comprehensive health check: device, firmware, system, and power state |
| `get_device_info`   | Retrieve hardware identifiers and manufacturing details               |
| `get_firmware_info` | Get firmware version and build metadata                               |
| `get_system_status` | Runtime system metrics (uptime, API SemVer, auto-update settings)     |
| `get_power_status`  | Battery charge level, voltage, current, and charging state            |

### Time tools

| Tool           | Description                                                        |
| -------------- | ------------------------------------------------------------------ |
| `get_time`     | Retrieve the device's real-time clock timestamp in ISO 8601 format |
| `get_timezone` | Get the currently configured timezone (name, offset, abbreviation) |
| `get_tzlist`   | List all supported timezones available for configuration           |

### Device Status & Configuration

| Tool                            | Description                                                                                                                         |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `get_ble_status`                | Retrieve BLE module status (powered state, MAC address)                                                                             |
| `enable_ble`                    | Enable the BLE module and start advertising                                                                                         |
| `disable_ble`                   | Disable the BLE module and stop advertising                                                                                         |
| `remove_ble_pairing`            | Remove the current BLE pairing so the device becomes discoverable again                                                             |
| `get_busy_snapshot`             | Get the current BUSY timer state including profile and timing details                                                               |
| `get_http_access`               | Inspect HTTP API key management mode and validity                                                                                   |
| `get_device_name`               | Get the human-readable device name                                                                                                  |
| `get_display_brightness`        | Retrieve the current display brightness level                                                                                       |
| `get_audio_volume`              | Retrieve the current audio volume level                                                                                             |
| `get_smart_home_pairing_status` | Query Matter fabric count and latest commissioning outcome                                                                          |
| `get_smart_home_switch_state`   | Read smart home relay/output state and startup behavior                                                                             |
| `list_storage_files`            | List files and directories on device storage (accepts optional path argument)                                                       |
| `get_storage_status`            | Get storage capacity details (used, free, total bytes)                                                                              |
| `get_firmware_update_status`    | Check currently installed firmware and pending update state                                                                         |
| `get_update_changelog`          | Retrieve release notes for a specific firmware version (accepts required version argument)                                          |
| `get_autoupdate_settings`       | Get automatic update configuration (enabled, window start/end)                                                                      |
| `get_wifi_status`               | Get Wi-Fi connection details (SSID, signal strength, channel, security)                                                             |
| `get_wifi_networks`             | Retrieve scanned available Wi-Fi networks in range (generated `WiFiApi.get_wifi_networks()` SDK call, GET /api/wifi/networks) |

### Input tools

| Tool             | Description                                                                                                |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
| `send_input_key` | Send a single key-press event to the device (up, down, ok, back, start, busy, custom, off, apps, settings) |

## Architecture

The project follows a thin-client layering with a clear separation between MCP presentation, configuration, and device access:

```text
┌───────────────────────────────┐      MCP/stdio       ┌──────────────────┐
│           MCP Client          │ ◄──────────────────► │   busybar_mcp    │
│         (AI tool)             │                      │                  │
└───────────────────────────────┘                      └────────┬─────────┘
                                                                │
                                      ┌─────────────────────────┼─────────────────────┐
                                      │                         │                     │
                                      ▼                         ▼                     ▼
                                  config.py                tools/              client.py
                                      │                         │                     │
                                      └─────────────────────────┼─────────────────────┘
                                                                │ SDK calls
                                                                ▼
                                                        busybar_python_sdk
                                                                │ HTTP
                                                                ▼
                                                          Busy Bar Device
```

### Package structure

```text
mcp_entry.py           # Repo-root dev-only entry file for mcp dev / MCP Inspector
busybar_mcp/
├── __init__.py       # Package API and entry point
├── __main__.py       # python -m busybar_mcp entry point
├── server.py         # MCP server and tool registration
├── client.py         # Busy Bar SDK client boundary
├── config.py         # Runtime configuration from environment
├── _server.py        # Backwards-compatible server import
├── utils.py          # Shared serialization/error utilities
└── tools/             # MCP tool modules and registration
```

The key responsibilities are:

- `server.py` — creates the MCP server, registers the tool package, and starts the stdio transport.
- `config.py` — reads `BUSYBAR_BASE_URL` and `BUSYBAR_API_TOKEN` from the process environment and validates that they are present.
- `client.py` — provides the boundary between the MCP application and `busybar_python_sdk`.
- `tools/` — contains the MCP tool implementations and their registration.
- `utils.py` — contains shared serialization and tool-error helpers.
- `__init__.py` / `__main__.py` — provide the package and command-line entry points.
- `mcp_entry.py` — repo-root dev-only entry file (loads `.env`, registers tools on `server`) for use with `mcp dev`; not part of the package or distribution.

At startup, `server.py` loads `.env` using `load_dotenv(override=False)`. This means exported environment variables are preserved and take precedence over `.env` values.

The SDK communicates with the device over HTTP using the OpenAPI schema defined by `openapi.yaml`.

## Testing

A working test suite is included with the project. The convenience runner at `run_tests.py` invokes pytest against the `tests/` directory with sensible defaults and no-header output.

### Run basic tests

```sh
python run_tests.py
```

### Run with coverage output

```sh
COVERAGE=1 python run_tests.py
```

Coverage is reported via `pytest-cov` (`--cov=busybar_mcp --cov-report=term-missing`).

### Test Coverage Summary

- **32 MCP tools** tested — one parametrized happy-path test per tool across account, system info, time, BLE, busy timer, settings, smart home, storage, update, wifi, and input categories.
- **Configuration tests** verify required environment variables and configuration parsing.
- **Error-path tests** verify behaviour when the Busy Bar device is missing or unreachable (mocked HTTP failures).
- **Missing environment variable tests** confirm that absent `BUSYBAR_BASE_URL` / `BUSYBAR_API_TOKEN` are handled gracefully.
- **Serialization tests** verify `_serialize()` handles SDK models, dicts, lists, scalars, and edge cases correctly.

## Publishing

Releases are automated by the GitHub Actions pipeline (`.github/workflows/publish.yml`), triggered on every push to `main`:

1. `pyproject.toml` metadata is synchronized (repository URL, testpypi index) via `scripts/sync_metadata.py`.
2. The project version is aligned with the `busybar-python-sdk` version (the first `X.Y.Z` token in its dependency specifier).
3. The updated commit is tagged `v<version>` (e.g., `v27.5.1`).
4. The package is built with `uv build` (sdist + wheel).
5. On success the artifacts are published to **test.pypi.org** via `uv publish --index testpypi` (never pypi.org).

Publishing requires a repository secret **`PyPI_API_TOKEN`** holding a test.pypi.org API token — obtain one at <https://test.pypi.org/legacy/token/> and add it under **Settings → Secrets and variables → Actions** on <https://github.com/nuxnik/busybar-mcp>. Runs where the version was already published perform no commit, tag, or upload (idempotent).

## What's Next

- Extend with additional write/mutation tools (display messages, notifications)
- Implement MCP resources for live device data streams
- Configure linting and formatting toolchain

TDQS

A3.9/5.0

Scored across 28 tools

Disambiguation3/5

Most tools target distinct endpoints, but there is overlap between the comprehensive get_device_status and its individual sub-getters (e.g., get_device_info, get_firmware_info, get_system_status, get_power_status), which could lead to redundant calls. Similarly, get_transport and get_wifi_status both relate to network connectivity, and the three account getters (info/status/backend) are closely related, though descriptions help differentiate them.

Naming Consistency4/5

The naming pattern is overwhelmingly consistent: almost every tool follows get_<resource>. The only deviations are list_storage_files (uses 'list' instead of 'get') and get_tzlist (a slightly non-standard noun form for a list), but these are minor and do not seriously disrupt the overall pattern.

Tool Count2/5

28 tools is above the threshold where a toolset starts to feel heavy; the calibration suggests 25+ tools is too many. While each tool corresponds to a distinct device endpoint, the sheer number of read-only getters makes the surface feel bloated and could overwhelm an agent, especially since many are minor status variations.

Completeness2/5

The tool surface is entirely read-only—there are no set, update, create, or delete operations. This is a significant gap for a device management server, as users cannot change brightness, volume, timezone, device name, firmware update settings, or perform any configuration action. The description of get_tzlist even references set_timezone, implying such functionality should exist but does not.

Maintenance

ActivityMaintained
ResponsivenessNo issues