@cyanheads/pixoo-mcp-server
Render and push styled pixel art, text, dashboards, and animations to Divoom Pixoo LED displays on your local network via MCP. STDIO or Streamable HTTP.
7 Tools • 4 Resources
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/pixoo-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/pixoo-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
[](https://github.com/cyanheads/pixoo-mcp-server/releases/latest/download/pixoo-mcp-server.mcpb) [](https://cursor.com/en/install-mcp?name=pixoo-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvcGl4b28tbWNwLXNlcnZlciJdfQ==) [](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22pixoo-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fpixoo-mcp-server%22%5D%7D)
[](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
---
## Overview
Divoom Pixoo LED matrix displays (Pixoo-64 primary; 16 and 32 also supported) on the local network. Render and push styled text, layered scenes, dashboards, and animations, or control device state, from any MCP client. Runs as a stdio process or a local Streamable HTTP server.
### Tools
| Tool | Description |
|:-----|:------------|
| `pixoo_display_text` | Render styled text (theme, gradient, shadow, outline, auto-fit) onto the display and push it. Returns the rendered frame as an image. |
| `pixoo_compose_scene` | Compose a full scene: layered elements (text, icons, widgets, shapes, bitmaps, images, sprites) with per-element effects and keyframes, static or animated. Returns the rendered scene as an image. |
| `pixoo_push_image` | Load an image (absolute local path or https URL), resize it to the LED grid, and push it. Returns the downsampled result as an image. |
| `pixoo_overlay_text` | Set or clear a device-native scrolling text overlay. Uses device-rendered fonts; overlays persist across channel switches until cleared. |
| `pixoo_control_device` | Read or change device state: brightness, screen on/off, channel, or clock face. Call with no params for a status read. |
| `pixoo_discover_devices` | Find Pixoo devices on the local network via Divoom's cloud discovery endpoint. Run once during setup to find device IPs. |
| `pixoo_design_brief` | Return craft guidance and live device context for a design topic. Covers legibility rules, palette discipline, layout zones, animation budget, and pre-filled next-tool suggestions. |
### Resources
| Resource | Description |
|:---|:---|
| `pixoo://device/status` | Live snapshot of the connected Pixoo display: reachable, channel, brightness, screen state, and display size |
| `pixoo://reference/themes` | Theme and palette registry with background gradients, default text palettes, accent colors, and swatch values |
| `pixoo://reference/icons` | Built-in icon names organized by category (weather, arrows, status, media) |
| `pixoo://reference/design-guide` | Long-form 64px craft guide: legibility floors, palette discipline, layout zones, animation budget, and known device behaviors |
All resource data is also reachable via tools. `pixoo_design_brief` surfaces the design guide content per topic; `pixoo_control_device` returns live device state equivalent to `pixoo://device/status`.
## Capability reference
### `pixoo_display_text` tool
- Named scene themes set background gradient and default text palette in one parameter (`midnight`, `ember`, `claude`, `ice`, `neon`, `forest`, `mono`)
- Style block: palette ramps (`ember`, `ice`, `neon`, `fire`, `lavender`, `claude`, `mono`) or a custom gradient/flat color, optional drop shadow, 1px outline, integer scale 1–8
- Semantic positioning (`x: "center"`, `y: "bottom"`) or absolute pixel coordinates; multi-line text stacks vertically with configurable alignment
- Auto-fit overflow tries standard font, then compact, then scroll; every fit decision is reported in `layout[]` with an `action` (`shrunk-to-compact`, `scrolling`, `wrapped`, `truncated`, `clipped`)
- Optional `brightness` (0–100) applied before push — a failure is a warning via an enrichment notice, not a tool error
- Returns the rendered frame as an image content block; `outputFiles` is populated only when `PIXOO_OUTPUT_DIR` is configured
---
### `pixoo_compose_scene` tool
- Up to 50 layered elements rendered back-to-front: `text`, `icon`, `rect`, `circle`, `line`, `progress`, `sparkline`, `bitmap`, `pixels`, `image`, `sprite`
- Background: solid color, gradient (vertical, horizontal, or radial), or named theme
- Animation via named effect presets (`float`, `scroll-left`, `scroll-right`, `pulse`, `blink`, `twinkle`, `drift`, `fade-in`, `fade-out`) or raw per-property keyframe arrays — 1–40 frames at 10–2000ms per frame (default 150ms)
- `image` and `sprite` elements accept an absolute local path or an https URL; a supplied `output` path must be absolute with no traversal segments
- Static scenes return a PNG preview; animations return a labeled contact-sheet PNG plus a saved GIF (GIF preview is inconsistent across MCP clients)
- Typed failures for `asset_not_found`, `invalid_color`, and `unknown_icon`, alongside the shared device-error reasons
---
### `pixoo_push_image` tool
- Accepts an absolute local file path or an https (not http) URL
- Three fit modes: `contain` (letterbox), `cover` (crop to fill), `fill` (stretch)
- Three resize kernels: `nearest` for pixel art (default), `lanczos3` for photos, `mitchell` for a balance
- Returns the exact resized result as an image content block before it is pushed
---
### `pixoo_overlay_text` tool
- `mode: "set"` adds or updates an overlay on one of 20 independent slots (`id` 0–19); `mode: "clear"` removes it
- 115 device-rendered font IDs (0–114); overlays persist across channel switches until explicitly cleared
- Configurable `x`/`y` (0–64), scroll `direction` (`left`/`right`), `speed` (0–100), and `align`; color is `#RRGGBB` hex only — named colors aren't supported here
- Device-rendered, not previewable — for styled, previewable text use `pixoo_display_text`
---
### `pixoo_control_device` tool
- Call with no params to read state only; supply any of `brightness` (0–100), `screen` (`on`/`off`), `channel` (`faces`/`cloud`/`visualizer`/`custom`), or `clockFaceId` to apply changes before the read-back
- `applied` lists which requested settings succeeded; a failed setting is omitted from `applied` and reported via an enrichment notice instead of failing the call
- Always returns current `reachable`, `channel`, `brightness`, `screenOn`, and `clockId` (the latter three absent when the device is unreachable)
---
### `pixoo_discover_devices` tool
- Queries Divoom's cloud discovery endpoint (`app.divoom-gz.com`) — requires internet access even for local device control
- Returns each device's name, numeric ID, and LAN IP to set as `PIXOO_IP`
- When `PIXOO_IP` is already configured, flags whether it matches a discovered device (`configuredIpFound`) and notes a mismatch
- `timeoutMs` configurable 1000–30000ms (default 5000ms)
---
### `pixoo_design_brief` tool
- Six topics: `text`, `scene`, `dashboard`, `animation`, `pixel-art`, `troubleshooting`
- Returns markdown craft guidance (legibility floors, palette discipline, layout zones, animation budgets) plus a live `deviceContext` snapshot
- `nextToolSuggestions` are pre-filled with ready-to-use arguments tailored to the topic and current device state (e.g. suggests `pixoo_discover_devices` when the device is unreachable)
- Also returns `availableThemes` and `iconCategories` for direct use in other tools
---
### `pixoo://device/status` resource
- Live snapshot: `reachable`, `channel`, `brightness`, `screenOn`, `clockId`, `displaySize`, `configuredIp`
- No cache — every read reaches the device; degrades to `reachable: false` instead of erroring when the device is unreachable
- Equivalent to calling `pixoo_control_device` with no params
---
### `pixoo://reference/themes` resource
- Every registered theme (background gradient or solid, default text palette, accent color, shadow flag) and every named palette (gradient stop pair)
- `themeNames` / `paletteNames` arrays for direct use in the `theme` / `palette` parameters
- Compile-time constants — cached for 24h
---
### `pixoo://reference/icons` resource
- Every built-in icon name, its category, and its SVG `viewBox`, plus a `byCategory` grouping (weather, arrows, status, media)
- Use a `name` from this registry in `pixoo_compose_scene` icon elements
- Compile-time constants — cached for 24h
---
### `pixoo://reference/design-guide` resource
- Long-form markdown: legibility floors, palette discipline, layout zones (top/middle/bottom strip pixel ranges), animation budget, pixel art rules, and known device behaviors (e.g. channel must be `custom` to show pushed content)
- `text/markdown` mime type; compile-time constant, cached for 24h
- Same content `pixoo_design_brief` surfaces per topic — this resource is the complete reference in one document
## Features
Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
Pixoo-specific:
- Requires a Divoom Pixoo LED matrix display on the local network; primary target is the Pixoo-64 (16 and 32 also supported)
- All composition happens in an RGBA canvas pipeline on the host (`@cyanheads/pixoo-toolkit`) — the device receives final RGB frames, never raw drawing commands
- Styled text engine: gradient palette ramps, drop shadows, outlines, integer scale, semantic alignment — no manual pixel math or bitmap letterforms required
- Push pacing: device commands serialized with a configurable minimum inter-push interval (default 1000ms) to prevent device freezes
- Animation capped at 40 frames (device instability beyond this); contact-sheet PNG preview for animations (GIF preview is inconsistent across MCP clients)
Agent-friendly output:
- Preview-as-content — render tools return the upscaled (8×, 512px) output as an image content block, so the calling model sees exactly what was drawn, before and after push
- Layout transparency — every silent renderer decision (font fallback, truncation, scroll engaged, element clipped) is reported in `layout[]` so agents can inspect and refine
- Device truth — `pushed` reflects the device ACK; `deviceState` after a push flags visibility issues (screen off, brightness ≤ 10, wrong channel) as enrichment notices rather than failures
- Graceful degradation — render succeeds and returns the preview even when the device is unreachable, so the agent keeps its work
## Getting started
Add the following to your MCP client configuration file. Run `pixoo_discover_devices` to find your Pixoo's IP, then set `PIXOO_IP` below.
```json
{
"mcpServers": {
"pixoo-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/pixoo-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"PIXOO_IP": "192.168.1.50"
}
}
}
}
```
Or with npx (no Bun required):
```json
{
"mcpServers": {
"pixoo-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/pixoo-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"PIXOO_IP": "192.168.1.50"
}
}
}
}
```
Or with Docker:
```json
{
"mcpServers": {
"pixoo-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "PIXOO_IP=192.168.1.50",
"ghcr.io/cyanheads/pixoo-mcp-server:latest"
]
}
}
}
```
For Streamable HTTP, set the transport and start the server:
```sh
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 PIXOO_IP=192.168.1.50 bun run start:http
# Server listens at http://localhost:3010/mcp
```
### Prerequisites
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
- A Divoom Pixoo LED matrix display on the local network (Pixoo-64, Pixoo-32, or Pixoo-16). Discovery tools and pure-render tools (`push: false`) work without a configured device.
### Installation
1. **Clone the repository:**
```sh
git clone https://github.com/cyanheads/pixoo-mcp-server.git
```
2. **Navigate into the directory:**
```sh
cd pixoo-mcp-server
```
3. **Install dependencies:**
```sh
bun install
```
4. **Configure environment:**
```sh
cp .env.example .env
# edit .env and set PIXOO_IP
```
## Configuration
All configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:
| Variable | Description | Default |
|:---------|:------------|:--------|
| `PIXOO_IP` | Device IP address on the local network. **Required for device tools** (`pixoo_display_text`, `pixoo_compose_scene`, `pixoo_push_image`, `pixoo_overlay_text`, `pixoo_control_device`). Discovery and pure-render (`push: false`) work without it. | — |
| `PIXOO_SIZE` | Display size in pixels: `16`, `32`, or `64`. | `64` |
| `PIXOO_OUTPUT_DIR` | Directory for auto-saving preview PNG and GIF files. When unset, previews are returned in-response only. | — |
| `PIXOO_PUSH_MIN_INTERVAL_MS` | Minimum interval between device pushes in milliseconds. Prevents device freeze from rapid-fire commands. | `1000` |
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
| `MCP_SESSION_MODE` | HTTP session handling: `stateful`, `stateless`, or `auto`. The server declares `stateless` in source — no tool requests input mid-call — and a value set here overrides it. | `stateless` |
| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
| `LOGS_DIR` | Directory for log files (Node.js only). | `/logs` |
| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |
| `OTEL_ENABLED` | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | `false` |
See [`.env.example`](./.env.example) for the full list of optional overrides.
## Running the server
### Local development
- **Build and run:**
```sh
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
```
- **Run checks and tests:**
```sh
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
```
### Docker
```sh
docker build -t pixoo-mcp-server .
docker run --rm -e PIXOO_IP=192.168.1.50 -p 3010:3010 pixoo-mcp-server
```
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/pixoo-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
## Project structure
| Directory | Purpose |
|:----------|:--------|
| `src/index.ts` | `createApp()` entry point — registers tools/resources and initializes the Pixoo service. |
| `src/config/` | Server-specific environment variable parsing and validation with Zod. |
| `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`). |
| `src/mcp-server/resources/` | Resource definitions (`*.resource.ts`). |
| `src/services/pixoo/` | `PixooService` — wraps `@cyanheads/pixoo-toolkit`, handles pacing, result mapping, and device state. |
| `src/renderer/` | Pure rendering pipeline: element renderers, styled-text engine, themes, icons, effect compiler, preview encoding. No device dependency. |
| `tests/` | Unit and integration tests mirroring `src/`. |
## Development guide
See [`CLAUDE.md`/`AGENTS.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no `try/catch` in tool logic
- Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage
- The renderer (`src/renderer/`) is pure — no device dependency, testable without hardware
- All device calls go through `PixooService`; every `PixooResult` is checked — never assume a push succeeded
## Contributing
Issues are welcome. Run checks and tests before submitting:
```sh
bun run devcheck
bun run test
```
## License
Apache-2.0 — see [LICENSE](LICENSE) for details.