@cyanheads/pixoo-mcp-server 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/AGENTS.md +10 -7
  2. package/CLAUDE.md +10 -7
  3. package/Dockerfile +30 -10
  4. package/README.md +65 -86
  5. package/changelog/1.2.x/1.2.1.md +35 -0
  6. package/changelog/1.2.x/1.2.2.md +40 -0
  7. package/dist/mcp-server/resources/definitions/pixoo-design-guide.resource.d.ts.map +1 -1
  8. package/dist/mcp-server/resources/definitions/pixoo-design-guide.resource.js +4 -1
  9. package/dist/mcp-server/resources/definitions/pixoo-design-guide.resource.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts +173 -13
  11. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts.map +1 -1
  12. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js +129 -28
  13. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js +15 -7
  16. package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts +2 -0
  18. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js +27 -12
  20. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js.map +1 -1
  21. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts +30 -1
  22. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts.map +1 -1
  23. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js +105 -26
  24. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js.map +1 -1
  25. package/dist/mcp-server/tools/finish-schema.d.ts +28 -0
  26. package/dist/mcp-server/tools/finish-schema.d.ts.map +1 -0
  27. package/dist/mcp-server/tools/finish-schema.js +42 -0
  28. package/dist/mcp-server/tools/finish-schema.js.map +1 -0
  29. package/dist/renderer/finish.d.ts +34 -0
  30. package/dist/renderer/finish.d.ts.map +1 -0
  31. package/dist/renderer/finish.js +62 -0
  32. package/dist/renderer/finish.js.map +1 -0
  33. package/dist/renderer/icons.d.ts +14 -4
  34. package/dist/renderer/icons.d.ts.map +1 -1
  35. package/dist/renderer/icons.js +30 -22
  36. package/dist/renderer/icons.js.map +1 -1
  37. package/dist/renderer/keyframes.d.ts +40 -4
  38. package/dist/renderer/keyframes.d.ts.map +1 -1
  39. package/dist/renderer/keyframes.js +100 -53
  40. package/dist/renderer/keyframes.js.map +1 -1
  41. package/dist/renderer/remote-image.d.ts +13 -7
  42. package/dist/renderer/remote-image.d.ts.map +1 -1
  43. package/dist/renderer/remote-image.js +17 -21
  44. package/dist/renderer/remote-image.js.map +1 -1
  45. package/dist/renderer/scene-renderer.d.ts +45 -6
  46. package/dist/renderer/scene-renderer.d.ts.map +1 -1
  47. package/dist/renderer/scene-renderer.js +328 -159
  48. package/dist/renderer/scene-renderer.js.map +1 -1
  49. package/dist/renderer/text-engine.d.ts +24 -5
  50. package/dist/renderer/text-engine.d.ts.map +1 -1
  51. package/dist/renderer/text-engine.js +51 -28
  52. package/dist/renderer/text-engine.js.map +1 -1
  53. package/package.json +7 -6
  54. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** pixoo-mcp-server
4
- **Version:** 1.2.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
4
+ **Version:** 1.2.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.9`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revision 2026-07-28 alongside the 2025 era)
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.1.0 (protocol revision 2026-07-28 alongside the 2025 era)
8
8
  **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -166,7 +166,7 @@ Handlers receive a unified `ctx` object. Key properties used by this server:
166
166
  | `ctx.enrich` | Success-path agent context — `.notice()` / `.total()` / `.echo()` / `.truncated()`. Lands only when the definition declares an `enrichment` block. |
167
167
  | `ctx.fail` | Typed throw against the definition's `errors[]` reason union — auto-populates `data.reason`. |
168
168
  | `ctx.recoveryFor` | `{ recovery: { hint } }` for a declared reason, resolved from the contract. Pass it as `ctx.fail`'s data argument (or spread it in) to put the declared hint on the wire. |
169
- | `ctx.state` | Tenant-scoped KV — `.get`, `.set(key, value, { ttl? })`, `.delete`, `.getMany`, `.list`. Keys are validated; colons are not legal separators. |
169
+ | `ctx.state` | Tenant-scoped KV — `.get`, `.set(key, value, { ttl? })`, `.delete`, `.getMany`, `.list`. Accepts any JSON-serializable value; reads return its JSON form (a `Date` comes back as an ISO string). Keys are validated; colons are not legal separators. |
170
170
  | `ctx.requestInput` / `ctx.inputs` | Multi-round-trip input. `return ctx.requestInput(...)`; read the answers with `ctx.inputs.accepted(key, schema)` on re-entry. Unused by this server. |
171
171
  | `ctx.signal` | `AbortSignal` for cancellation. Pass it to any long-running I/O. |
172
172
  | `ctx.requestId` | Unique request ID. |
@@ -187,6 +187,7 @@ Pixoo-specific error reasons declared on tools:
187
187
  | `device_rejected` | `ServiceUnavailable` | Firmware returned non-zero `error_code` | — |
188
188
  | `no_device_configured` | `InvalidParams` | Device tool called without `PIXOO_IP` | — |
189
189
  | `asset_not_found` | `NotFound` | Image/sprite path or URL unreadable | — |
190
+ | `invalid_image` | `InvalidParams` | Image source or sprite path read but not decodable | — |
190
191
  | `invalid_color` | `InvalidParams` | `resolveColor` throw — invalid color name or format | — |
191
192
  | `unknown_icon` | `InvalidParams` | Icon name not in registry | — |
192
193
  | `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable | `true` |
@@ -234,11 +235,13 @@ src/
234
235
  text-engine.ts # Gradient ramp + shadow + outline text engine, overflow handling
235
236
  scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
236
237
  keyframes.ts # Keyframe interpolation + animation preset compiler
238
+ finish.ts # Palette finish (quantize + dither) for one frame, or frames sharing one palette
237
239
  preview.ts # PNG/contact-sheet/GIF encoding
238
- remote-image.ts # https image fetch to a temp file for the toolkit loader; stops on ctx.signal
240
+ remote-image.ts # https image fetch into memory for the toolkit loader; stops on ctx.signal
239
241
  mcp-server/
240
242
  tools/
241
243
  device-push.ts # Shared post-render push: preview kept on failure, visibility notice
244
+ finish-schema.ts # Shared `finish` input schema (colors | palette, plus dither)
242
245
  tools/definitions/
243
246
  pixoo-display-text.tool.ts
244
247
  pixoo-compose-scene.tool.ts
@@ -332,7 +335,7 @@ Available skills:
332
335
  | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
333
336
  | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
334
337
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
335
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, MCPB `user_config` wiring, plugin manifests, README version badge (run by devcheck) |
338
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, MCPB `user_config` wiring, plugin manifests, manifest and README version parity, Dockerfile build platform (run by devcheck) |
336
339
  | `bun run list-skills` | Print the skill registry |
337
340
  | `bun run tree` | Generate directory structure doc |
338
341
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -352,7 +355,7 @@ Available skills:
352
355
 
353
356
  `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies and strips dependency-shipped agent docs and platform-specific native bindings that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP deployments are unaffected.
354
357
 
355
- `lint:packaging` verifies that `server.json` and `manifest.json` declare the same env var names, that every `manifest.json` `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.<key>}"` (the host substitutes nothing else), that an optional string option carries `"default": ""`, that no plugin manifest writes an empty `env` value, and that the README `Version-` badge matches `package.json`.
358
+ `lint:packaging` verifies that `server.json` and `manifest.json` declare the same env var names, that every `manifest.json` `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.<key>}"` (the host substitutes nothing else), that an optional string option carries `"default": ""`, that no plugin manifest writes an empty `env` value, that `manifest.json` and the README `Version-` badge carry the `package.json` version, and that the Dockerfile stage running `bun run build` starts `FROM --platform=$BUILDPLATFORM` (a multi-arch build otherwise runs it under QEMU, where Bun aborts).
356
359
 
357
360
  ---
358
361
 
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** pixoo-mcp-server
4
- **Version:** 1.2.0
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
4
+ **Version:** 1.2.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.9`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
- **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revision 2026-07-28 alongside the 2025 era)
7
+ **MCP SDK:** `@modelcontextprotocol/server` ^2.1.0 (protocol revision 2026-07-28 alongside the 2025 era)
8
8
  **Zod:** ^4.6.5
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -166,7 +166,7 @@ Handlers receive a unified `ctx` object. Key properties used by this server:
166
166
  | `ctx.enrich` | Success-path agent context — `.notice()` / `.total()` / `.echo()` / `.truncated()`. Lands only when the definition declares an `enrichment` block. |
167
167
  | `ctx.fail` | Typed throw against the definition's `errors[]` reason union — auto-populates `data.reason`. |
168
168
  | `ctx.recoveryFor` | `{ recovery: { hint } }` for a declared reason, resolved from the contract. Pass it as `ctx.fail`'s data argument (or spread it in) to put the declared hint on the wire. |
169
- | `ctx.state` | Tenant-scoped KV — `.get`, `.set(key, value, { ttl? })`, `.delete`, `.getMany`, `.list`. Keys are validated; colons are not legal separators. |
169
+ | `ctx.state` | Tenant-scoped KV — `.get`, `.set(key, value, { ttl? })`, `.delete`, `.getMany`, `.list`. Accepts any JSON-serializable value; reads return its JSON form (a `Date` comes back as an ISO string). Keys are validated; colons are not legal separators. |
170
170
  | `ctx.requestInput` / `ctx.inputs` | Multi-round-trip input. `return ctx.requestInput(...)`; read the answers with `ctx.inputs.accepted(key, schema)` on re-entry. Unused by this server. |
171
171
  | `ctx.signal` | `AbortSignal` for cancellation. Pass it to any long-running I/O. |
172
172
  | `ctx.requestId` | Unique request ID. |
@@ -187,6 +187,7 @@ Pixoo-specific error reasons declared on tools:
187
187
  | `device_rejected` | `ServiceUnavailable` | Firmware returned non-zero `error_code` | — |
188
188
  | `no_device_configured` | `InvalidParams` | Device tool called without `PIXOO_IP` | — |
189
189
  | `asset_not_found` | `NotFound` | Image/sprite path or URL unreadable | — |
190
+ | `invalid_image` | `InvalidParams` | Image source or sprite path read but not decodable | — |
190
191
  | `invalid_color` | `InvalidParams` | `resolveColor` throw — invalid color name or format | — |
191
192
  | `unknown_icon` | `InvalidParams` | Icon name not in registry | — |
192
193
  | `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable | `true` |
@@ -234,11 +235,13 @@ src/
234
235
  text-engine.ts # Gradient ramp + shadow + outline text engine, overflow handling
235
236
  scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
236
237
  keyframes.ts # Keyframe interpolation + animation preset compiler
238
+ finish.ts # Palette finish (quantize + dither) for one frame, or frames sharing one palette
237
239
  preview.ts # PNG/contact-sheet/GIF encoding
238
- remote-image.ts # https image fetch to a temp file for the toolkit loader; stops on ctx.signal
240
+ remote-image.ts # https image fetch into memory for the toolkit loader; stops on ctx.signal
239
241
  mcp-server/
240
242
  tools/
241
243
  device-push.ts # Shared post-render push: preview kept on failure, visibility notice
244
+ finish-schema.ts # Shared `finish` input schema (colors | palette, plus dither)
242
245
  tools/definitions/
243
246
  pixoo-display-text.tool.ts
244
247
  pixoo-compose-scene.tool.ts
@@ -332,7 +335,7 @@ Available skills:
332
335
  | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
333
336
  | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
334
337
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
335
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, MCPB `user_config` wiring, plugin manifests, README version badge (run by devcheck) |
338
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, MCPB `user_config` wiring, plugin manifests, manifest and README version parity, Dockerfile build platform (run by devcheck) |
336
339
  | `bun run list-skills` | Print the skill registry |
337
340
  | `bun run tree` | Generate directory structure doc |
338
341
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -352,7 +355,7 @@ Available skills:
352
355
 
353
356
  `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies and strips dependency-shipped agent docs and platform-specific native bindings that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP deployments are unaffected.
354
357
 
355
- `lint:packaging` verifies that `server.json` and `manifest.json` declare the same env var names, that every `manifest.json` `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.<key>}"` (the host substitutes nothing else), that an optional string option carries `"default": ""`, that no plugin manifest writes an empty `env` value, and that the README `Version-` badge matches `package.json`.
358
+ `lint:packaging` verifies that `server.json` and `manifest.json` declare the same env var names, that every `manifest.json` `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.<key>}"` (the host substitutes nothing else), that an optional string option carries `"default": ""`, that no plugin manifest writes an empty `env` value, that `manifest.json` and the README `Version-` badge carry the `package.json` version, and that the Dockerfile stage running `bun run build` starts `FROM --platform=$BUILDPLATFORM` (a multi-arch build otherwise runs it under QEMU, where Bun aborts).
356
359
 
357
360
  ---
358
361
 
package/Dockerfile CHANGED
@@ -9,7 +9,7 @@
9
9
  # is architecture-independent — emulating this stage buys nothing, and Bun 1.4
10
10
  # aborts with MemoryExhaustion under QEMU x86_64 when it is emulated.
11
11
  # ==============================================================================
12
- FROM --platform=$BUILDPLATFORM oven/bun:1.4.0 AS build
12
+ FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS build
13
13
 
14
14
  WORKDIR /usr/src/app
15
15
 
@@ -35,7 +35,7 @@ RUN bun run build
35
35
  # application. It uses a slim base image and only includes production
36
36
  # dependencies and build artifacts.
37
37
  # ==============================================================================
38
- FROM oven/bun:1.4.0-slim AS production
38
+ FROM oven/bun:1.4.2-slim AS production
39
39
 
40
40
  WORKDIR /usr/src/app
41
41
 
@@ -51,8 +51,15 @@ LABEL org.opencontainers.image.licenses="Apache-2.0"
51
51
  LABEL org.opencontainers.image.version="${APP_VERSION}"
52
52
  LABEL org.opencontainers.image.source="https://github.com/cyanheads/pixoo-mcp-server"
53
53
 
54
- # Copy dependency manifests
55
- COPY package.json bun.lock ./
54
+ # Copy dependency manifests. `bunfig.toml` rides along so every install below
55
+ # passes its release-age gate and security scanner, as a local install does.
56
+ COPY package.json bun.lock bunfig.toml ./
57
+
58
+ # The scanner bunfig.toml names is a devDependency, and Bun installs a missing
59
+ # scanner through the same production-filtered install, which omits it and
60
+ # aborts. Seed it from the build stage's full install instead. Remove this line
61
+ # together with the scanner if bunfig.toml stops naming one.
62
+ COPY --from=build /usr/src/app/node_modules/@socketsecurity/bun-security-scanner ./node_modules/@socketsecurity/bun-security-scanner
56
63
 
57
64
  # Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
58
65
  # that are not needed in the final production image.
@@ -65,21 +72,35 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
65
72
  bun install --production --omit=peer --frozen-lockfile --ignore-scripts
66
73
 
67
74
  # Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
68
- # These are not bundled by default to keep the base image lean. Enable at build time
69
- # with: docker build --build-arg OTEL_ENABLED=true
75
+ # Installed by default. Omit them for a leaner image at build time
76
+ # with: docker build --build-arg OTEL_ENABLED=false
77
+ # Each package is requested at the range the installed framework declares in
78
+ # `peerDependencies`, so the resolution stays inside the framework's tested
79
+ # peer range; a name with no declared range fails the build.
70
80
  ARG OTEL_ENABLED=true
71
81
  RUN --mount=type=cache,target=/root/.bun/install/cache \
72
82
  if [ "$OTEL_ENABLED" = "true" ]; then \
73
- bun add --omit=dev --omit=peer --ignore-scripts @hono/otel \
74
- @opentelemetry/instrumentation-http \
83
+ specs=$(bun -e ' \
84
+ const { peerDependencies: peers } = await Bun.file("node_modules/@cyanheads/mcp-ts-core/package.json").json(); \
85
+ const names = process.argv.slice(1); \
86
+ const missing = names.filter((name) => !peers?.[name]); \
87
+ if (missing.length > 0) throw new Error(`no peerDependencies range for ${missing.join(", ")}`); \
88
+ console.log(names.map((name) => `${name}@${peers[name]}`).join(" ")); \
89
+ ' \
90
+ @hono/otel \
91
+ @opentelemetry/api-logs \
92
+ @opentelemetry/exporter-logs-otlp-http \
75
93
  @opentelemetry/exporter-metrics-otlp-http \
76
94
  @opentelemetry/exporter-trace-otlp-http \
95
+ @opentelemetry/instrumentation-http \
77
96
  @opentelemetry/instrumentation-pino \
78
97
  @opentelemetry/resources \
98
+ @opentelemetry/sdk-logs \
79
99
  @opentelemetry/sdk-metrics \
80
100
  @opentelemetry/sdk-node \
81
101
  @opentelemetry/sdk-trace-node \
82
- @opentelemetry/semantic-conventions; \
102
+ @opentelemetry/semantic-conventions) \
103
+ && bun add --omit=dev --omit=peer --ignore-scripts $specs; \
83
104
  fi
84
105
 
85
106
  # Copy the compiled application code from the build stage
@@ -123,7 +144,6 @@ ENV MCP_TRANSPORT_TYPE="http"
123
144
  ENV MCP_SESSION_MODE="stateless"
124
145
  ENV MCP_LOG_LEVEL="info"
125
146
  ENV LOGS_DIR="/var/log/pixoo-mcp-server"
126
- ENV MCP_FORCE_CONSOLE_LOGGING="true"
127
147
 
128
148
  # Expose the port the server listens on
129
149
  EXPOSE ${MCP_HTTP_PORT}
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-1.2.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/pixoo-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/pixoo-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/pixoo-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-1.2.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/pixoo-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.1.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/pixoo-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/pixoo-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -23,130 +23,113 @@
23
23
 
24
24
  ## Overview
25
25
 
26
- 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.
26
+ Divoom Pixoo LED matrix displays on the local network, with the Pixoo-64 as the primary target and the 16 and 32 also supported. Render and push styled text, layered scenes, dashboards, and animations, or read and change device state. Runs as a stdio process or a local Streamable HTTP server.
27
27
 
28
28
  ### Tools
29
29
 
30
30
  | Tool | Description |
31
- |:-----|:------------|
32
- | `pixoo_display_text` | Render styled text (theme, gradient, shadow, outline, auto-fit) onto the display and push it, static or animated with a scroll, float, or pulse effect. Returns the render as an image. |
33
- | `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. |
34
- | `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. |
35
- | `pixoo_overlay_text` | Set or clear a device-native scrolling text overlay. Uses device-rendered fonts; overlays persist across channel switches until cleared. |
36
- | `pixoo_control_device` | Read or change device state: brightness, screen on/off, channel, or clock face. Call with no params for a status read. |
37
- | `pixoo_discover_devices` | Find Pixoo devices on the local network via Divoom's cloud discovery endpoint. Run once during setup to find device IPs. |
38
- | `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. |
31
+ |:---|:---|
32
+ | `pixoo_display_text` | Render styled text with themes, gradients, shadows, and auto-fit, static or animated, and push it |
33
+ | `pixoo_compose_scene` | Compose layered scenes of text, icons, widgets, shapes, bitmaps, images, and sprites, static or animated |
34
+ | `pixoo_push_image` | Resize a local or https image to the LED grid and push it, an animated GIF or WebP as an animation |
35
+ | `pixoo_overlay_text` | Set or clear a device-rendered scrolling text overlay |
36
+ | `pixoo_control_device` | Read or change brightness, screen state, channel, or clock face |
37
+ | `pixoo_discover_devices` | Find Pixoo devices and their LAN IPs through Divoom's cloud discovery |
38
+ | `pixoo_design_brief` | Craft guidance for a design topic, with live device state and pre-filled next calls |
39
39
 
40
40
  ### Resources
41
41
 
42
42
  | Resource | Description |
43
43
  |:---|:---|
44
- | `pixoo://device/status` | Live snapshot of the connected Pixoo display: reachable, channel, brightness, screen state, and display size |
45
- | `pixoo://reference/themes` | Theme and palette registry with background gradients, default text palettes, accent colors, and swatch values |
46
- | `pixoo://reference/icons` | Built-in icon names organized by category (weather, arrows, status, media) |
47
- | `pixoo://reference/design-guide` | Long-form 64px craft guide: legibility floors, palette discipline, layout zones, animation budget, and known device behaviors |
44
+ | `pixoo://device/status` | Live device snapshot: reachability, channel, brightness, screen state, display size |
45
+ | `pixoo://reference/themes` | Theme and palette registry |
46
+ | `pixoo://reference/icons` | Built-in icon names by category |
47
+ | `pixoo://reference/design-guide` | Long-form craft guide for the 64px display |
48
48
 
49
- 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`.
49
+ Tools cover the same ground for tool-only clients: `pixoo_control_device` reads the live device state, and `pixoo_design_brief` returns craft guidance, theme names, and icon names.
50
50
 
51
51
  ## Capability reference
52
52
 
53
53
  ### `pixoo_display_text` <sub>tool</sub>
54
54
 
55
- - Named scene themes set background gradient and default text palette in one parameter (`midnight`, `ember`, `claude`, `ice`, `neon`, `forest`, `mono`)
56
- - 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
57
- - Semantic positioning (`x: "center"`, `y: "bottom"`) or absolute pixel coordinates; multi-line text stacks vertically, and `align` (`left`/`center`/`right`) lines up the lines within the block that `position.x` places
58
- - Auto-fit overflow tries standard font, then compact, then marks the text `scrolling`; an explicit `font` is used as given, so text too wide for it scrolls in that font instead of shrinking; every fit decision is reported in `layout[]` with an `action` (`shrunk-to-compact`, `scrolling`, `wrapped`, `truncated`, `clipped`)
59
- - `effect` animates the text: `scroll` runs it across the display once (up to 40 frames), `auto` scrolls only text too wide to fit, `float` and `pulse` loop over 20 frames; animations push as one device animation and report `frames`
60
- - Optional `brightness` (0–100) applied before push — a failure is a warning via an enrichment notice, not a tool error
61
- - Returns the rendered frame (or a grid of every animation frame) as an image content block; `outputFiles` (PNG, or GIF when animated) is populated only when `PIXOO_OUTPUT_DIR` is configured
55
+ - `text` as a string or an array of lines; `theme` (`midnight`, `ember`, `claude`, `ice`, `neon`, `forest`, `mono`) sets the background and default palette. `style` takes a `palette` ramp (`ember`, `ice`, `neon`, `fire`, `lavender`, `claude`, `mono`) or a custom `{ from, to }`, plus `shadow`, `outline`, and `scale` 1–8; `position` is semantic or in pixels, and `align` lines up multi-line text
56
+ - `font`: `standard` (5×7) and `compact` (3×5) draw printable ASCII plus `° ← ↑ → ↓ ▲ ▼ ♥ · …`; `numerals` is an 11×18 digit face for clocks and readouts that draws 0–9, space, and `: . - + / % ° ?`, and text holding any other character fails validation, naming those characters
57
+ - `layout[]` reports every fit decision as an `action` (`none`, `shrunk-to-compact`, or `scrolling`), the `font` used, and whether each line's box `fits` on the panel; a scrolling line's box starts at x 0, where its static frame draws it. Single-line text falls back from the standard to the compact font unless `font` is set — never to `numerals` — and text still too wide only scrolls under `effect: "auto"` or `"scroll"`
58
+ - `effect`: `scroll` makes one pass in up to 40 frames, `auto` scrolls only on overflow, and `float` and `pulse` loop over 20 frames; `frames` reports the count, and the animation pushes as one device animation
62
59
 
63
60
  ---
64
61
 
65
62
  ### `pixoo_compose_scene` <sub>tool</sub>
66
63
 
67
- - Up to 50 layered elements rendered back-to-front: `text`, `icon`, `rect`, `circle`, `line`, `progress`, `sparkline`, `bitmap`, `pixels`, `image`, `sprite`
68
- - Background: solid color, gradient (vertical, horizontal, or radial), or named theme
69
- - 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)
70
- - `image` elements accept an absolute local path or an https URL and fit the configured display size; `sprite` elements take an absolute local path; a supplied `output` path must be absolute with no traversal segments, and replaces the `PIXOO_OUTPUT_DIR` auto-save for that call
71
- - `opacity` (0–100) blends any element, images included, over the layers beneath it
72
- - Static scenes return a PNG preview; animations return a contact-sheet PNG tiling every frame, plus a GIF saved to `PIXOO_OUTPUT_DIR` when configured (GIF preview is inconsistent across MCP clients)
73
- - Typed failures for `asset_not_found`, `invalid_color`, and `unknown_icon`, alongside the shared device-error reasons
64
+ - Up to 50 `elements` drawn back-to-front: `text` (in the same three fonts as `pixoo_display_text`), `icon`, `rect`, `circle`, `line`, `progress`, `sparkline`, `bitmap`, `pixels`, `image` (absolute path or https URL, with the same `finish` as `pixoo_push_image`), `sprite` (absolute path). The `background` is a solid color, a `v` / `h` / `r` gradient, or a `theme`
65
+ - Every element takes `opacity` (each pixel lands at its own alpha × `opacity`, so soft edges fade evenly) and `blend`: `normal`, `add` (glows and light beams), `screen`, or `multiply`. `line` and outline `circle` take `strokeWidth` and `antialias`, and a `rect` border takes `strokeWidth`, growing inward; either field on a shape that draws no stroke fails validation, naming it
66
+ - Returns `layout[]`: each element's placed box — for a wide or anti-aliased stroke, every pixel it draws — and whether it `fits` on the panel. An absolute `output` path saves the first frame as a PNG in place of the `PIXOO_OUTPUT_DIR` auto-save. Typed failures: `asset_not_found`, `invalid_image` (an image or sprite that was read but does not decode), `invalid_color`, `unknown_icon`, `invalid_output_path`
67
+ - Animation through per-element `effect` presets (`float`, `scroll-left`, `scroll-right`, `pulse`, `blink`, `twinkle`, `drift`, `fade-in`, `fade-out`) or raw `animate` keyframes over `dx`, `dy`, `opacity` (numbers or numeric strings), `visible` (`true`/`false`), and `color` (interpolated through RGB on any element with a `color`), each track holding at least one keyframe; `frames` 1–40, `speed` 10–2000 ms per frame (default 150). An effect's `amplitude` sets the movement of `float`, `scroll-*`, and `drift`, and the 0–1 depth of the `pulse` and `twinkle` opacity dip
74
68
 
75
69
  ---
76
70
 
77
71
  ### `pixoo_push_image` <sub>tool</sub>
78
72
 
79
- - Accepts an absolute local file path or an https (not http) URL; a URL response is capped at 10 MB, enforced while the body streams, and the download stops when the call is cancelled
80
- - Three fit modes: `contain` (letterbox), `cover` (crop to fill), `fill` (stretch)
81
- - Three resize kernels: `nearest` for pixel art (default), `lanczos3` for photos, `mitchell` for a balance
82
- - Returns the exact resized result as an image content block before it is pushed
73
+ - `source` is an absolute local path or an https URL, with downloads capped at 10 MB; `fit` is `contain` (default), `cover`, or `fill`, and `kernel` is `nearest` (default, for pixel art), `lanczos3` (photos), or `mitchell`
74
+ - A source that decodes as an animated GIF or WebP, whatever its file name, pushes as an animation of up to 40 frames, sampled evenly from a longer source. It plays at the source's total duration over the pushed frame count (150 ms when the source records no delays), or at `speed` (10–2000 ms per frame); `frames`, `sourceFrames`, and `speed` report what was pushed
75
+ - `finish` reduces the image to a palette before the push: exactly one of `colors` (2–256, built from the image) or `palette` (1–256 hex or named colors), plus `dither` (`none`, `bayer4`, `floyd-steinberg`). Transparent pixels stay unlit, and an animation's `colors` palette is shared by every frame
76
+ - An unreadable path or URL fails as `asset_not_found`, a source that is read but does not decode (a text file, an HTML page, a truncated download) fails as `invalid_image`, and an unresolvable `finish` palette entry fails as `invalid_color`; the preview is the exact frame the device receives, or a grid of every frame for an animation
83
77
 
84
78
  ---
85
79
 
86
80
  ### `pixoo_overlay_text` <sub>tool</sub>
87
81
 
88
- - `mode: "set"` adds or updates an overlay on one of 20 independent slots (`id` 0–19); `mode: "clear"` removes it
89
- - 115 device-rendered font IDs (0–114); overlays persist across channel switches until explicitly cleared
90
- - Configurable `x`/`y` (0 to display size − 1, checked against `PIXOO_SIZE`), scroll `direction` (`left`/`right`), `speed` (0–100), and `align`; color accepts hex (`#RRGGBB` or `#RGB`) or a named color, the same as the render tools
91
- - Device-rendered, not previewable — for styled, previewable text use `pixoo_display_text`
82
+ - `mode: "set"` or `"clear"` on one of 20 slots (`id` 0–19). `set` requires `text` and takes a device `font` ID (0–114), `x` / `y` within `PIXOO_SIZE`, `color`, `speed` (0–100), `direction`, `align`, and `width`
83
+ - Returns `acknowledged`, `mode`, and `id`. The device renders the overlay, so there is no preview, and it persists across channel switches until cleared
92
84
 
93
85
  ---
94
86
 
95
87
  ### `pixoo_control_device` <sub>tool</sub>
96
88
 
97
- - 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
98
- - `applied` lists which requested settings succeeded; a failed setting is omitted from `applied` and reported in a `notice` instead of failing the call — every failed setting in one call, each with its failure kind and message
99
- - Always returns current `reachable`, `channel`, `brightness`, `screenOn`, and `clockId` (the latter three absent when the device is unreachable)
89
+ - No params reads state; any of `brightness` (0–100), `screen` (`on` / `off`), `channel` (`faces` / `cloud` / `visualizer` / `custom`), or `clockFaceId` is applied before the read-back
90
+ - Returns `reachable`, `channel`, `brightness`, `screenOn`, and `clockId` (absent when unreachable) plus `applied`; a failed setting is left out of `applied` and named in the notice instead of failing the call
100
91
 
101
92
  ---
102
93
 
103
94
  ### `pixoo_discover_devices` <sub>tool</sub>
104
95
 
105
- - Queries Divoom's cloud discovery endpoint (`app.divoom-gz.com`) — requires internet access even for local device control
106
- - Returns each device's name, numeric ID, and LAN IP to set as `PIXOO_IP`
107
- - When `PIXOO_IP` is already configured, flags whether it matches a discovered device (`configuredIpFound`) and notes a mismatch
108
- - `timeoutMs` configurable 1000–30000ms (default 5000ms)
96
+ - Queries Divoom's cloud endpoint (`app.divoom-gz.com`), so it needs internet access; `timeoutMs` 1000–30000 (default 5000)
97
+ - Returns each device's `name`, `id`, and `ip`; with `PIXOO_IP` set, `configuredIpFound` says whether it matched. An unreachable endpoint fails as `discovery_failed`
109
98
 
110
99
  ---
111
100
 
112
101
  ### `pixoo_design_brief` <sub>tool</sub>
113
102
 
114
- - Six topics: `text`, `scene`, `dashboard`, `animation`, `pixel-art`, `troubleshooting`
115
- - Returns markdown craft guidance (legibility floors, palette discipline, layout zones, animation budgets) plus a live `deviceContext` snapshot
116
- - `nextToolSuggestions` entries are `{ toolName, reason, args }`, with `args` pre-filled with ready-to-use arguments (`{}` when the tool needs none) tailored to the topic and current device state (e.g. suggests `pixoo_discover_devices` when the device is unreachable)
117
- - Also returns `availableThemes` and `iconCategories` for direct use in other tools
103
+ - `topic`: `text`, `scene`, `dashboard`, `animation`, `pixel-art`, or `troubleshooting`; works without a reachable device
104
+ - Returns markdown `craftGuidance`, a live `deviceContext`, `nextToolSuggestions` as `{ toolName, reason, args }` with arguments pre-filled for the topic and device state, plus `availableThemes` and `iconCategories`
118
105
 
119
106
  ---
120
107
 
121
108
  ### `pixoo://device/status` <sub>resource</sub>
122
109
 
123
- - Live snapshot: `reachable`, `channel`, `brightness`, `screenOn`, `clockId`, `displaySize`, `configuredIp`
124
- - No cache — every read reaches the device; degrades to `reachable: false` instead of erroring when the device is unreachable
125
- - Equivalent to calling `pixoo_control_device` with no params
110
+ - Uncached live read: `reachable`, `channel`, `brightness`, `screenOn`, `clockId`, `displaySize`, `configuredIp`
111
+ - Returns `reachable: false` rather than an error when the device can't be reached or `PIXOO_IP` is unset
126
112
 
127
113
  ---
128
114
 
129
115
  ### `pixoo://reference/themes` <sub>resource</sub>
130
116
 
131
- - Every registered theme (background gradient or solid, default text palette, accent color, shadow flag) and every named palette (gradient stop pair)
132
- - `themeNames` / `paletteNames` arrays for direct use in the `theme` / `palette` parameters
133
- - Compile-time constants — cached for 24h
117
+ - Every theme (background, `textPalette`, `accent`, `shadow`) and palette (`from` / `to` stops), plus `themeNames` and `paletteNames` for the `theme` and `palette` parameters
118
+ - Static registry, cached for 24h
134
119
 
135
120
  ---
136
121
 
137
122
  ### `pixoo://reference/icons` <sub>resource</sub>
138
123
 
139
- - Every built-in icon name, its category, and its SVG `viewBox`, plus a `byCategory` grouping (weather, arrows, status, media)
140
- - Use a `name` from this registry in `pixoo_compose_scene` icon elements
141
- - Compile-time constants — cached for 24h
124
+ - Each icon's `name`, `category`, and `viewBox`, plus a `byCategory` grouping (weather, arrows, status, media); a `name` goes in a `pixoo_compose_scene` icon element
125
+ - Static registry, cached for 24h
142
126
 
143
127
  ---
144
128
 
145
129
  ### `pixoo://reference/design-guide` <sub>resource</sub>
146
130
 
147
- - 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)
148
- - `text/markdown` mime type; compile-time constant, cached for 24h
149
- - Same content `pixoo_design_brief` surfaces per topic — this resource is the complete reference in one document
131
+ - `text/markdown`: legibility floors, palette discipline, layout zones, animation budget, effect presets, pixel art rules, push pacing, and known device behaviors
132
+ - The whole guide in one document, where `pixoo_design_brief` returns guidance per topic; cached for 24h
150
133
 
151
134
  ## Features
152
135
 
@@ -154,22 +137,20 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
154
137
 
155
138
  Pixoo-specific:
156
139
 
157
- - Requires a Divoom Pixoo LED matrix display on the local network; primary target is the Pixoo-64 (16 and 32 also supported)
158
- - All composition happens in an RGBA canvas pipeline on the host (`@cyanheads/pixoo-toolkit`) — the device receives final RGB frames, never raw drawing commands
159
- - Styled text engine: gradient palette ramps, drop shadows, outlines, integer scale, semantic alignment — no manual pixel math or bitmap letterforms required
160
- - Push pacing: device commands serialized with a configurable minimum inter-push interval (default 1000ms) to prevent device freezes
161
- - Animation capped at 40 frames (device instability beyond this); contact-sheet PNG preview for animations (GIF preview is inconsistent across MCP clients)
140
+ - All composition happens on the host in an RGBA canvas pipeline (`@cyanheads/pixoo-toolkit`); the device receives finished RGB frames
141
+ - Pushes switch the device to the custom channel and run one at a time, spaced by `PIXOO_PUSH_MIN_INTERVAL_MS` (default 1000) so rapid pushes don't freeze the device
142
+ - Animations cap at 40 frames, past which the device becomes unstable; `pixoo_push_image` samples a longer GIF or WebP down to 40
162
143
 
163
144
  Agent-friendly output:
164
145
 
165
- - 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
166
- - Layout transparency — every silent renderer decision (font fallback, truncation, scroll engaged, element clipped) is reported in `layout[]` so agents can inspect and refine
167
- - Device truth — `pushed` reflects the device ACK; `deviceState` after a push reports the channel, brightness, and screen state the device returned, with a `notice` naming the fix when the render won't be visible (screen off, brightness ≤ 10, device off the custom channel)
168
- - Render without a device — `push: false` renders and returns the preview with no device reachable; a push to an unreachable device fails with a retryable `device_unreachable` error whose `outputFiles` names the saved preview (the `PIXOO_OUTPUT_DIR` copy when configured, otherwise a temp file), so the render isn't lost
146
+ - Preview on every render: `pixoo_display_text`, `pixoo_compose_scene`, and `pixoo_push_image` return the frame as an 8× upscaled PNG image block, pushed or not, so `push: false` checks a design with no device attached. Animations preview as a grid of every frame, since GIF display varies across MCP clients; the GIF itself is saved to `PIXOO_OUTPUT_DIR` when set
147
+ - Layout transparency: `layout[]` reports every renderer decision (font fallback, truncation, scrolling, clipping) so agents can refine a design
148
+ - Device truth: `pushed` reflects the device ACK, and the `deviceState` read back after a push comes with a notice naming the fix when the render won't be visible (screen off, brightness ≤ 10, off the custom channel)
149
+ - Renders survive failed pushes: the typed error (`device_unreachable`, `device_http_error`, `device_rejected`, `no_device_configured`) carries `outputFiles` pointing at the saved preview (the `PIXOO_OUTPUT_DIR` copy, or a temp file when that is unset)
169
150
 
170
151
  ## Getting started
171
152
 
172
- Add the following to your MCP client configuration file. Run `pixoo_discover_devices` to find your Pixoo's IP, then set `PIXOO_IP` below.
153
+ Add the following to your MCP client configuration file, with `PIXOO_IP` set to your Pixoo's LAN address. `pixoo_discover_devices` finds it if you don't know it.
173
154
 
174
155
  ```json
175
156
  {
@@ -236,7 +217,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 PIXOO_IP=192.168.1.50 bun run start:h
236
217
  ### Prerequisites
237
218
 
238
219
  - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
239
- - 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.
220
+ - A Divoom Pixoo on the local network (Pixoo-64, Pixoo-32, or Pixoo-16).
240
221
 
241
222
  ### Installation
242
223
 
@@ -267,22 +248,20 @@ cp .env.example .env
267
248
 
268
249
  ## Configuration
269
250
 
270
- All configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:
271
-
272
251
  | Variable | Description | Default |
273
- |:---------|:------------|:--------|
274
- | `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. | — |
252
+ |:---|:---|:---|
253
+ | `PIXOO_IP` | Device IP on the local network. **Required** for pushes, overlays, and device control; discovery, design briefs, and `push: false` renders work without it. | — |
275
254
  | `PIXOO_SIZE` | Display size in pixels: `16`, `32`, or `64`. | `64` |
276
- | `PIXOO_OUTPUT_DIR` | Directory for auto-saving preview PNG and GIF files. When unset, previews are returned in-response only (a failed push still writes its preview to a temp file). An explicit `output` on `pixoo_compose_scene` replaces it for that call. | — |
277
- | `PIXOO_PUSH_MIN_INTERVAL_MS` | Minimum interval between device pushes in milliseconds. Prevents device freeze from rapid-fire commands. | `1000` |
255
+ | `PIXOO_OUTPUT_DIR` | Directory where render tools save preview PNG and GIF files. Unset, previews are returned only in the response. | — |
256
+ | `PIXOO_PUSH_MIN_INTERVAL_MS` | Minimum gap between device pushes, in ms. | `1000` |
278
257
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
279
258
  | `MCP_HTTP_PORT` | HTTP server port. | `3010` |
280
- | `MCP_SESSION_MODE` | HTTP session handling: `stateful`, `stateless`, or `auto` (the framework's schema default, which resolves to `stateful`). The server declares `stateless` in source — no tool requests input mid-call — and a value set here overrides it. | `stateless` |
259
+ | `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto`. A value set here overrides the server's declared `stateless`. | `stateless` |
281
260
  | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
282
261
  | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
283
262
  | `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
284
- | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |
285
- | `OTEL_ENABLED` | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | `false` |
263
+ | `LOG_TOOL_FAILURE_PAYLOADS` | Log each failed tool call's arguments and result, redacted by key name and capped at `LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES` (default `16384`). A secret inside a free-form value is not redacted. | `false` |
264
+ | `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
286
265
 
287
266
  See [`.env.example`](./.env.example) for the full list of optional overrides.
288
267
 
@@ -317,18 +296,18 @@ docker build -t pixoo-mcp-server .
317
296
  docker run --rm -e PIXOO_IP=192.168.1.50 -p 3010:3010 pixoo-mcp-server
318
297
  ```
319
298
 
320
- 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.
299
+ 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.
321
300
 
322
301
  ## Project structure
323
302
 
324
303
  | Directory | Purpose |
325
- |:----------|:--------|
326
- | `src/index.ts` | `createApp()` entry point — registers tools/resources and initializes the Pixoo service. |
304
+ |:---|:---|
305
+ | `src/index.ts` | `createApp()` entry point: registers tools and resources and initializes the Pixoo service. |
327
306
  | `src/config/` | Server-specific environment variable parsing and validation with Zod. |
328
- | `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`). |
307
+ | `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`), the shared post-render push path, and the shared `finish` input schema. |
329
308
  | `src/mcp-server/resources/` | Resource definitions (`*.resource.ts`). |
330
- | `src/services/pixoo/` | `PixooService` — wraps `@cyanheads/pixoo-toolkit`, handles pacing, result mapping, and device state. |
331
- | `src/renderer/` | Pure rendering pipeline: element renderers, styled-text engine, themes, icons, effect compiler, preview encoding. No device dependency. |
309
+ | `src/services/pixoo/` | `PixooService`: wraps `@cyanheads/pixoo-toolkit` with push pacing, result mapping, and device state reads. |
310
+ | `src/renderer/` | Pure rendering pipeline with no device dependency: element renderers, styled-text engine, themes, icons, effect compiler, palette finishing, preview encoding, remote image fetch. |
332
311
  | `tests/` | Unit and integration tests mirroring `src/`. |
333
312
 
334
313
  ## Development guide
@@ -0,0 +1,35 @@
1
+ ---
2
+ summary: "pixoo_compose_scene checks animate keyframes per property and caps image, icon, and sprite sizes; stroke icons, icon palettes, keyframed colors, and layout fits now render and report as documented. mcp-ts-core ^0.13.6 → ^0.13.9."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 1.2.1 — 2026-09-26
8
+
9
+ ## Changed
10
+
11
+ - **`animate` keyframe values are checked per property** — `dx`, `dy`, and `opacity` take numbers or numeric strings, `visible` takes `true`/`false` only (numbers are now rejected), and a bad value fails `-32602` naming the keyframe ([#47](https://github.com/cyanheads/pixoo-mcp-server/issues/47)). An empty track fails the same way ([#45](https://github.com/cyanheads/pixoo-mcp-server/issues/45)).
12
+ - **Element sizes are capped** — `image` and `icon` `w`/`h` must be 1–256, and sprite `cols`, `rows`, and `scale` at most 64; out-of-range values fail `-32602` naming the field ([#33](https://github.com/cyanheads/pixoo-mcp-server/issues/33), [#37](https://github.com/cyanheads/pixoo-mcp-server/issues/37), [#38](https://github.com/cyanheads/pixoo-mcp-server/issues/38)).
13
+ - **Registry icons draw their stroke parts as 1-pixel lines** — `snow` and `wind` render, `sun`, `rain`, and the arrows keep their rays, drops, and shafts, and the status icons draw as a ring plus mark instead of a solid disk ([#39](https://github.com/cyanheads/pixoo-mcp-server/issues/39)).
14
+ - **`layout[].fits` is computed from the placed box on all four edges** for every element; `image` and `line` boxes include `dx`/`dy`, `circle` and `line` boxes their last pixel, a `bitmap` box spans its widest row, and a `pixels` box bounds its points ([#42](https://github.com/cyanheads/pixoo-mcp-server/issues/42)).
15
+ - **Effect `amplitude` sets the depth of the `pulse` and `twinkle` opacity dip** on a 0–1 scale; the defaults (0.5, 0.6) keep the 50–100 and 40–100 ranges ([#41](https://github.com/cyanheads/pixoo-mcp-server/issues/41)).
16
+ - **A keyframed `color` interpolates and drives every element with a `color` field** — in-between frames resolve as `#rrggbb` instead of failing `invalid_color`, and `text`, `icon`, `rect`, `circle`, `line`, and `sparkline` animate it, not only `pixels` ([#36](https://github.com/cyanheads/pixoo-mcp-server/issues/36)).
17
+ - **Framework argument handling (mcp-ts-core 0.13.7–0.13.9)** — an integer sent to a string field, or a JSON-stringified object, is repaired before validation; rejections name the field path; stack traces and request context no longer reach client error data.
18
+ - **Opt-in `LOG_TOOL_FAILURE_PAYLOADS` and `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`** log failed calls' arguments and results and export logs over OTLP; the Docker image installs the log-export peers.
19
+ - Repo hygiene: framework skills and scripts resynced with mcp-ts-core 0.13.9; the npm tarball excludes `dist/*.mcpb`; `.mcpbignore` excludes `docs/idea.md` and `logs/`.
20
+
21
+ ## Fixed
22
+
23
+ - **`Object.prototype` names such as `constructor` or `__proto__` as an icon `name` fail as `unknown_icon`** instead of an internal `-32603` ([#32](https://github.com/cyanheads/pixoo-mcp-server/issues/32)). The same names as color values are not covered yet.
24
+ - **Icon `palette` paints a top-to-bottom ramp** from the palette's `from` to its `to` color and takes precedence over `color`; it was accepted and ignored ([#35](https://github.com/cyanheads/pixoo-mcp-server/issues/35)).
25
+ - **A `color` keyframe that isn't a color fails as `invalid_color` wherever it sits in the track**, including past the scene's last frame ([#40](https://github.com/cyanheads/pixoo-mcp-server/issues/40)).
26
+ - **Numeric-string keyframes on `dx`, `dy`, and `opacity` interpolate like the numbers they spell** (`"000"` → `"100"` ramps 0 → 100); only the `color` track lerps through RGB ([#43](https://github.com/cyanheads/pixoo-mcp-server/issues/43)).
27
+ - **A line-mode `sparkline` stays inside its `w × h` box** instead of inking one column and one row past it ([#44](https://github.com/cyanheads/pixoo-mcp-server/issues/44)).
28
+ - **The `music` icon's beam joins its two stems** ([#46](https://github.com/cyanheads/pixoo-mcp-server/issues/46)).
29
+
30
+ ## Dependencies
31
+
32
+ - `@cyanheads/mcp-ts-core` ^0.13.6 → ^0.13.9 (`@modelcontextprotocol/server` 2.0.0 → 2.1.0, transitive)
33
+ - `@socketsecurity/bun-security-scanner` ^1.1.2 → ^1.1.3 (dev)
34
+ - `ignore` ^7.0.9 → ^7.0.10 (dev)
35
+ - Bun 1.4.0 → 1.4.2 (`packageManager` and Docker base images)