@cyanheads/pixoo-mcp-server 1.1.4 → 1.2.1

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 (59) hide show
  1. package/AGENTS.md +11 -7
  2. package/CLAUDE.md +11 -7
  3. package/Dockerfile +30 -10
  4. package/README.md +61 -85
  5. package/changelog/1.2.x/1.2.0.md +28 -0
  6. package/changelog/1.2.x/1.2.1.md +35 -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 +1 -0
  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 +77 -11
  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 +69 -59
  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 +3 -3
  15. package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js +57 -45
  17. package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts +1 -0
  19. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js +121 -32
  21. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.d.ts.map +1 -1
  23. package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.js +17 -3
  24. package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.js.map +1 -1
  25. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts.map +1 -1
  26. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js +9 -8
  27. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js.map +1 -1
  28. package/dist/mcp-server/tools/device-push.d.ts +24 -0
  29. package/dist/mcp-server/tools/device-push.d.ts.map +1 -0
  30. package/dist/mcp-server/tools/device-push.js +50 -0
  31. package/dist/mcp-server/tools/device-push.js.map +1 -0
  32. package/dist/renderer/icons.d.ts +14 -4
  33. package/dist/renderer/icons.d.ts.map +1 -1
  34. package/dist/renderer/icons.js +30 -22
  35. package/dist/renderer/icons.js.map +1 -1
  36. package/dist/renderer/keyframes.d.ts +35 -3
  37. package/dist/renderer/keyframes.d.ts.map +1 -1
  38. package/dist/renderer/keyframes.js +85 -52
  39. package/dist/renderer/keyframes.js.map +1 -1
  40. package/dist/renderer/preview.d.ts +17 -19
  41. package/dist/renderer/preview.d.ts.map +1 -1
  42. package/dist/renderer/preview.js +55 -34
  43. package/dist/renderer/preview.js.map +1 -1
  44. package/dist/renderer/remote-image.d.ts +10 -2
  45. package/dist/renderer/remote-image.d.ts.map +1 -1
  46. package/dist/renderer/remote-image.js +13 -1
  47. package/dist/renderer/remote-image.js.map +1 -1
  48. package/dist/renderer/scene-renderer.d.ts +14 -9
  49. package/dist/renderer/scene-renderer.d.ts.map +1 -1
  50. package/dist/renderer/scene-renderer.js +166 -137
  51. package/dist/renderer/scene-renderer.js.map +1 -1
  52. package/dist/renderer/text-engine.d.ts +14 -1
  53. package/dist/renderer/text-engine.d.ts.map +1 -1
  54. package/dist/renderer/text-engine.js +22 -4
  55. package/dist/renderer/text-engine.js.map +1 -1
  56. package/dist/services/pixoo/pixoo-service.js +1 -1
  57. package/dist/services/pixoo/pixoo-service.js.map +1 -1
  58. package/package.json +6 -5
  59. 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.1.4
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
4
+ **Version:** 1.2.1
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. |
@@ -195,6 +195,8 @@ Pixoo-specific error reasons declared on tools:
195
195
 
196
196
  A contract's `retryable` reaches the wire only through `ctx.fail`; a service throw carries it only when the service writes `data.retryable` itself. `classifyDeviceFailure` (in `pixoo-service.ts`) is the one place a failed device call becomes a reason and a retryability — `PixooService` writes both on its push-path throws, and `pixoo_overlay_text` passes the retryability into `ctx.fail` so it overrides the contract default per occurrence.
197
197
 
198
+ The three push tools (`pixoo_display_text`, `pixoo_compose_scene`, `pixoo_push_image`) share one post-render path in `src/mcp-server/tools/device-push.ts`. `pushKeepingPreview` rethrows a failed push's error with its code, `reason`, `retryable`, and recovery untouched, adding `data.outputFiles` (the file the call already saved, else a copy in a fresh `os.tmpdir()` directory) and naming the path in the message. The framework drops `ctx.content` blocks from error results, so the file path is how the render survives. On success, `visibilityNotice` turns the post-push `DeviceStateSnapshot` into one `ctx.enrich.notice` (screen off, brightness ≤ 10, not on the custom channel). `ctx.enrich.notice` is last-wins, so a tool with a second notice source composes them into one string.
199
+
198
200
  ```ts
199
201
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
200
202
 
@@ -233,8 +235,10 @@ src/
233
235
  scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
234
236
  keyframes.ts # Keyframe interpolation + animation preset compiler
235
237
  preview.ts # PNG/contact-sheet/GIF encoding
236
- remote-image.ts # https image fetch to a temp file for the toolkit loader
238
+ remote-image.ts # https image fetch to a temp file for the toolkit loader; stops on ctx.signal
237
239
  mcp-server/
240
+ tools/
241
+ device-push.ts # Shared post-render push: preview kept on failure, visibility notice
238
242
  tools/definitions/
239
243
  pixoo-display-text.tool.ts
240
244
  pixoo-compose-scene.tool.ts
@@ -328,7 +332,7 @@ Available skills:
328
332
  | `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` |
329
333
  | `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` |
330
334
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
331
- | `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) |
335
+ | `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) |
332
336
  | `bun run list-skills` | Print the skill registry |
333
337
  | `bun run tree` | Generate directory structure doc |
334
338
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -348,7 +352,7 @@ Available skills:
348
352
 
349
353
  `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.
350
354
 
351
- `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`.
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, 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).
352
356
 
353
357
  ---
354
358
 
package/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** pixoo-mcp-server
4
- **Version:** 1.1.4
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
4
+ **Version:** 1.2.1
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. |
@@ -195,6 +195,8 @@ Pixoo-specific error reasons declared on tools:
195
195
 
196
196
  A contract's `retryable` reaches the wire only through `ctx.fail`; a service throw carries it only when the service writes `data.retryable` itself. `classifyDeviceFailure` (in `pixoo-service.ts`) is the one place a failed device call becomes a reason and a retryability — `PixooService` writes both on its push-path throws, and `pixoo_overlay_text` passes the retryability into `ctx.fail` so it overrides the contract default per occurrence.
197
197
 
198
+ The three push tools (`pixoo_display_text`, `pixoo_compose_scene`, `pixoo_push_image`) share one post-render path in `src/mcp-server/tools/device-push.ts`. `pushKeepingPreview` rethrows a failed push's error with its code, `reason`, `retryable`, and recovery untouched, adding `data.outputFiles` (the file the call already saved, else a copy in a fresh `os.tmpdir()` directory) and naming the path in the message. The framework drops `ctx.content` blocks from error results, so the file path is how the render survives. On success, `visibilityNotice` turns the post-push `DeviceStateSnapshot` into one `ctx.enrich.notice` (screen off, brightness ≤ 10, not on the custom channel). `ctx.enrich.notice` is last-wins, so a tool with a second notice source composes them into one string.
199
+
198
200
  ```ts
199
201
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
200
202
 
@@ -233,8 +235,10 @@ src/
233
235
  scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
234
236
  keyframes.ts # Keyframe interpolation + animation preset compiler
235
237
  preview.ts # PNG/contact-sheet/GIF encoding
236
- remote-image.ts # https image fetch to a temp file for the toolkit loader
238
+ remote-image.ts # https image fetch to a temp file for the toolkit loader; stops on ctx.signal
237
239
  mcp-server/
240
+ tools/
241
+ device-push.ts # Shared post-render push: preview kept on failure, visibility notice
238
242
  tools/definitions/
239
243
  pixoo-display-text.tool.ts
240
244
  pixoo-compose-scene.tool.ts
@@ -328,7 +332,7 @@ Available skills:
328
332
  | `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` |
329
333
  | `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` |
330
334
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
331
- | `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) |
335
+ | `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) |
332
336
  | `bun run list-skills` | Print the skill registry |
333
337
  | `bun run tree` | Generate directory structure doc |
334
338
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -348,7 +352,7 @@ Available skills:
348
352
 
349
353
  `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.
350
354
 
351
- `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`.
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, 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).
352
356
 
353
357
  ---
354
358
 
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.1.4-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.1-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,129 +23,109 @@
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. Returns the rendered frame 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 |
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 with configurable alignment
58
- - 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`)
59
- - Optional `brightness` (0–100) applied before push — a failure is a warning via an enrichment notice, not a tool error
60
- - Returns the rendered frame as an image content block; `outputFiles` 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
+ - `layout[]` reports every fit decision as an `action` (`shrunk-to-compact`, `scrolling`, `truncated`, …). Single-line text falls back from the standard to the compact font unless `font` is set, and text still too wide only scrolls under `effect: "auto"` or `"scroll"`
57
+ - `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
61
58
 
62
59
  ---
63
60
 
64
61
  ### `pixoo_compose_scene` <sub>tool</sub>
65
62
 
66
- - Up to 50 layered elements rendered back-to-front: `text`, `icon`, `rect`, `circle`, `line`, `progress`, `sparkline`, `bitmap`, `pixels`, `image`, `sprite`
67
- - Background: solid color, gradient (vertical, horizontal, or radial), or named theme
68
- - 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)
69
- - `image` elements accept an absolute local path or an https URL; `sprite` elements take an absolute local path; a supplied `output` path must be absolute with no traversal segments
70
- - `opacity` (0–100) blends any element, images included, over the layers beneath it
71
- - Static scenes return a PNG preview; animations return a labeled contact-sheet PNG plus a saved GIF (GIF preview is inconsistent across MCP clients)
72
- - Typed failures for `asset_not_found`, `invalid_color`, and `unknown_icon`, alongside the shared device-error reasons
63
+ - Up to 50 `elements` drawn back-to-front: `text`, `icon`, `rect`, `circle`, `line`, `progress`, `sparkline`, `bitmap`, `pixels`, `image` (absolute path or https URL), `sprite` (absolute path). The `background` is a solid color, a `v` / `h` / `r` gradient, or a `theme`
64
+ - Returns `layout[]`: each element's placed box 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_color`, `unknown_icon`, `invalid_output_path`
65
+ - 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
73
66
 
74
67
  ---
75
68
 
76
69
  ### `pixoo_push_image` <sub>tool</sub>
77
70
 
78
- - 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
79
- - Three fit modes: `contain` (letterbox), `cover` (crop to fill), `fill` (stretch)
80
- - Three resize kernels: `nearest` for pixel art (default), `lanczos3` for photos, `mitchell` for a balance
81
- - Returns the exact resized result as an image content block before it is pushed
71
+ - `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`
72
+ - An unreadable path or URL fails as `asset_not_found`; the preview is the exact resized frame the device receives
82
73
 
83
74
  ---
84
75
 
85
76
  ### `pixoo_overlay_text` <sub>tool</sub>
86
77
 
87
- - `mode: "set"` adds or updates an overlay on one of 20 independent slots (`id` 0–19); `mode: "clear"` removes it
88
- - 115 device-rendered font IDs (0–114); overlays persist across channel switches until explicitly cleared
89
- - Configurable `x`/`y` (0–64), 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
90
- - Device-rendered, not previewable — for styled, previewable text use `pixoo_display_text`
78
+ - `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`
79
+ - Returns `acknowledged`, `mode`, and `id`. The device renders the overlay, so there is no preview, and it persists across channel switches until cleared
91
80
 
92
81
  ---
93
82
 
94
83
  ### `pixoo_control_device` <sub>tool</sub>
95
84
 
96
- - 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
97
- - `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
98
- - Always returns current `reachable`, `channel`, `brightness`, `screenOn`, and `clockId` (the latter three absent when the device is unreachable)
85
+ - 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
86
+ - 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
99
87
 
100
88
  ---
101
89
 
102
90
  ### `pixoo_discover_devices` <sub>tool</sub>
103
91
 
104
- - Queries Divoom's cloud discovery endpoint (`app.divoom-gz.com`) — requires internet access even for local device control
105
- - Returns each device's name, numeric ID, and LAN IP to set as `PIXOO_IP`
106
- - When `PIXOO_IP` is already configured, flags whether it matches a discovered device (`configuredIpFound`) and notes a mismatch
107
- - `timeoutMs` configurable 1000–30000ms (default 5000ms)
92
+ - Queries Divoom's cloud endpoint (`app.divoom-gz.com`), so it needs internet access; `timeoutMs` 1000–30000 (default 5000)
93
+ - Returns each device's `name`, `id`, and `ip`; with `PIXOO_IP` set, `configuredIpFound` says whether it matched. An unreachable endpoint fails as `discovery_failed`
108
94
 
109
95
  ---
110
96
 
111
97
  ### `pixoo_design_brief` <sub>tool</sub>
112
98
 
113
- - Six topics: `text`, `scene`, `dashboard`, `animation`, `pixel-art`, `troubleshooting`
114
- - Returns markdown craft guidance (legibility floors, palette discipline, layout zones, animation budgets) plus a live `deviceContext` snapshot
115
- - `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)
116
- - Also returns `availableThemes` and `iconCategories` for direct use in other tools
99
+ - `topic`: `text`, `scene`, `dashboard`, `animation`, `pixel-art`, or `troubleshooting`; works without a reachable device
100
+ - 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`
117
101
 
118
102
  ---
119
103
 
120
104
  ### `pixoo://device/status` <sub>resource</sub>
121
105
 
122
- - Live snapshot: `reachable`, `channel`, `brightness`, `screenOn`, `clockId`, `displaySize`, `configuredIp`
123
- - No cache — every read reaches the device; degrades to `reachable: false` instead of erroring when the device is unreachable
124
- - Equivalent to calling `pixoo_control_device` with no params
106
+ - Uncached live read: `reachable`, `channel`, `brightness`, `screenOn`, `clockId`, `displaySize`, `configuredIp`
107
+ - Returns `reachable: false` rather than an error when the device can't be reached or `PIXOO_IP` is unset
125
108
 
126
109
  ---
127
110
 
128
111
  ### `pixoo://reference/themes` <sub>resource</sub>
129
112
 
130
- - Every registered theme (background gradient or solid, default text palette, accent color, shadow flag) and every named palette (gradient stop pair)
131
- - `themeNames` / `paletteNames` arrays for direct use in the `theme` / `palette` parameters
132
- - Compile-time constants — cached for 24h
113
+ - Every theme (background, `textPalette`, `accent`, `shadow`) and palette (`from` / `to` stops), plus `themeNames` and `paletteNames` for the `theme` and `palette` parameters
114
+ - Static registry, cached for 24h
133
115
 
134
116
  ---
135
117
 
136
118
  ### `pixoo://reference/icons` <sub>resource</sub>
137
119
 
138
- - Every built-in icon name, its category, and its SVG `viewBox`, plus a `byCategory` grouping (weather, arrows, status, media)
139
- - Use a `name` from this registry in `pixoo_compose_scene` icon elements
140
- - Compile-time constants — cached for 24h
120
+ - 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
121
+ - Static registry, cached for 24h
141
122
 
142
123
  ---
143
124
 
144
125
  ### `pixoo://reference/design-guide` <sub>resource</sub>
145
126
 
146
- - 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)
147
- - `text/markdown` mime type; compile-time constant, cached for 24h
148
- - Same content `pixoo_design_brief` surfaces per topic — this resource is the complete reference in one document
127
+ - `text/markdown`: legibility floors, palette discipline, layout zones, animation budget, effect presets, pixel art rules, push pacing, and known device behaviors
128
+ - The whole guide in one document, where `pixoo_design_brief` returns guidance per topic; cached for 24h
149
129
 
150
130
  ## Features
151
131
 
@@ -153,22 +133,20 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
153
133
 
154
134
  Pixoo-specific:
155
135
 
156
- - Requires a Divoom Pixoo LED matrix display on the local network; primary target is the Pixoo-64 (16 and 32 also supported)
157
- - All composition happens in an RGBA canvas pipeline on the host (`@cyanheads/pixoo-toolkit`) — the device receives final RGB frames, never raw drawing commands
158
- - Styled text engine: gradient palette ramps, drop shadows, outlines, integer scale, semantic alignment — no manual pixel math or bitmap letterforms required
159
- - Push pacing: device commands serialized with a configurable minimum inter-push interval (default 1000ms) to prevent device freezes
160
- - Animation capped at 40 frames (device instability beyond this); contact-sheet PNG preview for animations (GIF preview is inconsistent across MCP clients)
136
+ - All composition happens on the host in an RGBA canvas pipeline (`@cyanheads/pixoo-toolkit`); the device receives finished RGB frames
137
+ - 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
138
+ - Animations cap at 40 frames, past which the device becomes unstable
161
139
 
162
140
  Agent-friendly output:
163
141
 
164
- - 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
165
- - Layout transparency — every silent renderer decision (font fallback, truncation, scroll engaged, element clipped) is reported in `layout[]` so agents can inspect and refine
166
- - Device truth — `pushed` reflects the device ACK; `deviceState` after a push reports the channel, brightness, and screen state the device returned
167
- - 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
142
+ - 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
143
+ - Layout transparency: `layout[]` reports every renderer decision (font fallback, truncation, scrolling, clipping) so agents can refine a design
144
+ - 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)
145
+ - 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)
168
146
 
169
147
  ## Getting started
170
148
 
171
- Add the following to your MCP client configuration file. Run `pixoo_discover_devices` to find your Pixoo's IP, then set `PIXOO_IP` below.
149
+ 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.
172
150
 
173
151
  ```json
174
152
  {
@@ -235,7 +213,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 PIXOO_IP=192.168.1.50 bun run start:h
235
213
  ### Prerequisites
236
214
 
237
215
  - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
238
- - 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.
216
+ - A Divoom Pixoo on the local network (Pixoo-64, Pixoo-32, or Pixoo-16).
239
217
 
240
218
  ### Installation
241
219
 
@@ -266,22 +244,20 @@ cp .env.example .env
266
244
 
267
245
  ## Configuration
268
246
 
269
- All configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:
270
-
271
247
  | Variable | Description | Default |
272
- |:---------|:------------|:--------|
273
- | `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. | — |
248
+ |:---|:---|:---|
249
+ | `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. | — |
274
250
  | `PIXOO_SIZE` | Display size in pixels: `16`, `32`, or `64`. | `64` |
275
- | `PIXOO_OUTPUT_DIR` | Directory for auto-saving preview PNG and GIF files. When unset, previews are returned in-response only. | — |
276
- | `PIXOO_PUSH_MIN_INTERVAL_MS` | Minimum interval between device pushes in milliseconds. Prevents device freeze from rapid-fire commands. | `1000` |
251
+ | `PIXOO_OUTPUT_DIR` | Directory where render tools save preview PNG and GIF files. Unset, previews are returned only in the response. | — |
252
+ | `PIXOO_PUSH_MIN_INTERVAL_MS` | Minimum gap between device pushes, in ms. | `1000` |
277
253
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
278
254
  | `MCP_HTTP_PORT` | HTTP server port. | `3010` |
279
- | `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` |
255
+ | `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto`. A value set here overrides the server's declared `stateless`. | `stateless` |
280
256
  | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
281
257
  | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
282
258
  | `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
283
- | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |
284
- | `OTEL_ENABLED` | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | `false` |
259
+ | `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` |
260
+ | `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
285
261
 
286
262
  See [`.env.example`](./.env.example) for the full list of optional overrides.
287
263
 
@@ -316,18 +292,18 @@ docker build -t pixoo-mcp-server .
316
292
  docker run --rm -e PIXOO_IP=192.168.1.50 -p 3010:3010 pixoo-mcp-server
317
293
  ```
318
294
 
319
- 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.
295
+ 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.
320
296
 
321
297
  ## Project structure
322
298
 
323
299
  | Directory | Purpose |
324
- |:----------|:--------|
325
- | `src/index.ts` | `createApp()` entry point — registers tools/resources and initializes the Pixoo service. |
300
+ |:---|:---|
301
+ | `src/index.ts` | `createApp()` entry point: registers tools and resources and initializes the Pixoo service. |
326
302
  | `src/config/` | Server-specific environment variable parsing and validation with Zod. |
327
- | `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`). |
303
+ | `src/mcp-server/tools/` | Tool definitions (`*.tool.ts`) and the shared post-render push path. |
328
304
  | `src/mcp-server/resources/` | Resource definitions (`*.resource.ts`). |
329
- | `src/services/pixoo/` | `PixooService` — wraps `@cyanheads/pixoo-toolkit`, handles pacing, result mapping, and device state. |
330
- | `src/renderer/` | Pure rendering pipeline: element renderers, styled-text engine, themes, icons, effect compiler, preview encoding. No device dependency. |
305
+ | `src/services/pixoo/` | `PixooService`: wraps `@cyanheads/pixoo-toolkit` with push pacing, result mapping, and device state reads. |
306
+ | `src/renderer/` | Pure rendering pipeline with no device dependency: element renderers, styled-text engine, themes, icons, effect compiler, preview encoding, remote image fetch. |
331
307
  | `tests/` | Unit and integration tests mirroring `src/`. |
332
308
 
333
309
  ## Development guide
@@ -0,0 +1,28 @@
1
+ ---
2
+ summary: "pixoo_display_text adds scroll/float/pulse effects, align, and an honored font; animated previews return a full frame grid; failed pushes keep their render; design_brief's suggestion shape and overlay_text's x/y/width bounds change (breaking); scene image sizing and per-source caching are fixed."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 1.2.0 — 2026-09-23
8
+
9
+ ## Added
10
+
11
+ - **`pixoo_display_text` gains `effect: "scroll" | "float" | "pulse"`** — animates the text and pushes it as a multi-frame animation; `effect: "auto"` scrolls only when the text overflows ([#3](https://github.com/cyanheads/pixoo-mcp-server/issues/3)).
12
+ - **`align: "left" | "center" | "right"`** on `pixoo_display_text` lines up multi-line text within the block `position.x` places ([#3](https://github.com/cyanheads/pixoo-mcp-server/issues/3)).
13
+ - **An explicit `font` on `pixoo_display_text` is honored on single-line text** instead of always starting from the 5×7 font ([#20](https://github.com/cyanheads/pixoo-mcp-server/issues/20)).
14
+ - **`pixoo_display_text` and `pixoo_compose_scene` animations return a real contact-sheet grid** tiling every frame, instead of the single middle frame ([#6](https://github.com/cyanheads/pixoo-mcp-server/issues/6)).
15
+ - **A successful push the panel won't show now carries a notice** — screen off, brightness ≤ 10, or the device off the Custom channel — naming the `pixoo_control_device` call that fixes it ([#16](https://github.com/cyanheads/pixoo-mcp-server/issues/16)).
16
+
17
+ ## Changed
18
+
19
+ - **`nextToolSuggestions` on `pixoo_design_brief` is now `{ toolName, reason, args }`**, replacing `{ tool, rationale, suggestedArgs? }`; `args` is required. Breaking ([#10](https://github.com/cyanheads/pixoo-mcp-server/issues/10)).
20
+ - **An explicit `output` on `pixoo_compose_scene` replaces the `PIXOO_OUTPUT_DIR` auto-save** for that call instead of writing both ([#9](https://github.com/cyanheads/pixoo-mcp-server/issues/9)).
21
+ - **A failed push now keeps its render** — the preview's path (the auto-save, or a fresh temp file) rides the error as `data.outputFiles` instead of being lost ([#17](https://github.com/cyanheads/pixoo-mcp-server/issues/17)).
22
+ - Remote image downloads on `pixoo_push_image` and `pixoo_compose_scene` now cancel mid-stream when the calling request is cancelled ([#18](https://github.com/cyanheads/pixoo-mcp-server/issues/18)).
23
+
24
+ ## Fixed
25
+
26
+ - **`pixoo_overlay_text` rejects `x`/`y` at or beyond the configured `PIXOO_SIZE` and `width` above it** — the schema's `.max(64)` ceiling can't see the runtime size on 16/32px devices. Breaking: `x`/`y` equal to the display size, previously accepted off-canvas, is now rejected ([#8](https://github.com/cyanheads/pixoo-mcp-server/issues/8)).
27
+ - **`pixoo_compose_scene` `image` elements fit the configured `PIXOO_SIZE`** instead of always loading onto a 64px canvas ([#15](https://github.com/cyanheads/pixoo-mcp-server/issues/15)).
28
+ - **Two `image` elements sharing a source now render independently** instead of both taking whichever element's placement loaded last ([#21](https://github.com/cyanheads/pixoo-mcp-server/issues/21)).
@@ -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)
@@ -1 +1 @@
1
- {"version":3,"file":"pixoo-design-guide.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/pixoo-design-guide.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAuGrD,eAAO,MAAM,wBAAwB,2GAyBnC,CAAC"}
1
+ {"version":3,"file":"pixoo-design-guide.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/pixoo-design-guide.resource.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAwGrD,eAAO,MAAM,wBAAwB,2GAyBnC,CAAC"}
@@ -99,6 +99,7 @@ At typical viewing distance for a 64×64 LED display:
99
99
  ## Known Device Behaviors
100
100
 
101
101
  - **Channel must be Custom** to display pushed content. The server auto-switches when pushing.
102
+ - **A push the panel won't show** — screen off, brightness 10 or lower, or still off Custom after a failed switch — succeeds with a notice naming the pixoo_control_device call that fixes it.
102
103
  - **getConfig() omits SelectIndex** on current Pixoo-64 firmware — use getChannel() for reliable channel reads
103
104
  - **Text overlays (pixoo_overlay_text)** persist across channel switches until explicitly cleared
104
105
  - **Discovery requires internet** — Divoom cloud endpoint even for local device control