@cyanheads/pixoo-mcp-server 1.2.2 → 1.3.0
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.
- package/AGENTS.md +13 -9
- package/CLAUDE.md +13 -9
- package/Dockerfile +63 -46
- package/README.md +7 -7
- package/changelog/1.3.x/1.3.0.md +35 -0
- package/dist/config/server-config.d.ts +1 -1
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +6 -1
- package/dist/config/server-config.js.map +1 -1
- package/dist/mcp-server/resources/definitions/pixoo-design-guide.resource.js +1 -1
- package/dist/mcp-server/tools/asset-source-schema.d.ts +13 -0
- package/dist/mcp-server/tools/asset-source-schema.d.ts.map +1 -0
- package/dist/mcp-server/tools/asset-source-schema.js +25 -0
- package/dist/mcp-server/tools/asset-source-schema.js.map +1 -0
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts +43 -53
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js +102 -74
- package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-discover-devices.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-discover-devices.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-discover-devices.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts +6 -9
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js +26 -20
- package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.js +1 -3
- package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js +5 -6
- package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js.map +1 -1
- package/dist/renderer/remote-image.d.ts +1 -1
- package/dist/renderer/remote-image.d.ts.map +1 -1
- package/dist/renderer/remote-image.js +6 -6
- package/dist/renderer/remote-image.js.map +1 -1
- package/dist/renderer/scene-renderer.d.ts +14 -8
- package/dist/renderer/scene-renderer.d.ts.map +1 -1
- package/dist/renderer/scene-renderer.js +17 -17
- package/dist/renderer/scene-renderer.js.map +1 -1
- package/dist/renderer/text-engine.d.ts +22 -12
- package/dist/renderer/text-engine.d.ts.map +1 -1
- package/dist/renderer/text-engine.js +38 -26
- package/dist/renderer/text-engine.js.map +1 -1
- package/dist/services/pixoo/pixoo-service.d.ts.map +1 -1
- package/dist/services/pixoo/pixoo-service.js +9 -33
- package/dist/services/pixoo/pixoo-service.js.map +1 -1
- package/package.json +2 -2
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** pixoo-mcp-server
|
|
4
|
-
**Version:** 1.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.
|
|
4
|
+
**Version:** 1.3.0
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.10`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.1.0 (protocol revision 2026-07-28 alongside the 2025 era)
|
|
8
8
|
**Zod:** ^4.6.5
|
|
@@ -118,6 +118,7 @@ export const pixooDeviceStatusResource = resource('pixoo://device/status', {
|
|
|
118
118
|
|
|
119
119
|
```ts
|
|
120
120
|
// src/config/server-config.ts
|
|
121
|
+
import * as path from 'node:path';
|
|
121
122
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
122
123
|
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
123
124
|
|
|
@@ -126,7 +127,8 @@ const ServerConfigSchema = z.object({
|
|
|
126
127
|
pixooSize: z.coerce.number()
|
|
127
128
|
.refine((v) => v === 16 || v === 32 || v === 64)
|
|
128
129
|
.default(64).describe('Display size in pixels (16, 32, or 64).'),
|
|
129
|
-
pixooOutputDir: z.string().
|
|
130
|
+
pixooOutputDir: z.string().transform((dir) => path.resolve(dir)).optional()
|
|
131
|
+
.describe('Auto-save directory for preview PNG/GIF files; a relative path resolves against the launch directory.'),
|
|
130
132
|
pixooPushMinIntervalMs: z.coerce.number().int().min(0).default(1000)
|
|
131
133
|
.describe('Pacing floor between device pushes in milliseconds.'),
|
|
132
134
|
});
|
|
@@ -164,12 +166,11 @@ Handlers receive a unified `ctx` object. Key properties used by this server:
|
|
|
164
166
|
|:---------|:------------|
|
|
165
167
|
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
|
|
166
168
|
| `ctx.enrich` | Success-path agent context — `.notice()` / `.total()` / `.echo()` / `.truncated()`. Lands only when the definition declares an `enrichment` block. |
|
|
167
|
-
| `ctx.fail` | Typed throw against the definition's `errors[]` reason union — auto-populates `data.reason`. |
|
|
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.fail` | Typed throw against the definition's `errors[]` reason union — auto-populates `data.reason`; the framework fills in the entry's `recovery` as `data.recovery.hint`. |
|
|
169
170
|
| `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
171
|
| `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
172
|
| `ctx.signal` | `AbortSignal` for cancellation. Pass it to any long-running I/O. |
|
|
172
|
-
| `ctx.requestId` |
|
|
173
|
+
| `ctx.requestId` | Request ID — the one every log record of the call carries and its error envelope returns as `data.requestId`. |
|
|
173
174
|
| `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
|
|
174
175
|
|
|
175
176
|
---
|
|
@@ -192,7 +193,7 @@ Pixoo-specific error reasons declared on tools:
|
|
|
192
193
|
| `unknown_icon` | `InvalidParams` | Icon name not in registry | — |
|
|
193
194
|
| `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable | `true` |
|
|
194
195
|
|
|
195
|
-
`PixooService` and `src/renderer/` raise the device, configuration, and asset reasons themselves (a factory error carrying `data.reason`), so those entries carry `thrownBy: 'service'` — lint-only metadata that keeps `error-contract-unthrown` from reading them as dead. A reason the handler throws with `ctx.fail` stays unmarked
|
|
196
|
+
`PixooService` and `src/renderer/` raise the device, configuration, and asset reasons themselves (a factory error carrying `data.reason`), so those entries carry `thrownBy: 'service'` — lint-only metadata that keeps `error-contract-unthrown` from reading them as dead. A reason the handler throws with `ctx.fail` stays unmarked. Either way, the framework puts the entry's `recovery` on the wire when a failure carrying that `reason` arrives without a hint — a bare `ctx.fail('reason', message)` or a service throw with `data: { reason }` — as `data.recovery.hint`, mirrored into `content[]` unless the message already contains it. Pass an explicit `{ recovery: { hint } }` only to override it for one occurrence — a throw-site hint wins over the contract, so wording that holds for every occurrence of a reason lives in its `errors[]` entry, not at the throw. Every error envelope also carries `data.requestId`, and `content[]` closes with `(reason … · request <id>)`.
|
|
196
197
|
|
|
197
198
|
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.
|
|
198
199
|
|
|
@@ -212,7 +213,7 @@ errors: [
|
|
|
212
213
|
],
|
|
213
214
|
|
|
214
215
|
// in the handler
|
|
215
|
-
throw ctx.fail('invalid_color', `Invalid color "${input.color}"
|
|
216
|
+
throw ctx.fail('invalid_color', `Invalid color "${input.color}"`);
|
|
216
217
|
```
|
|
217
218
|
|
|
218
219
|
Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring. A tool argument that fails the input schema reaches the client as `InvalidParams` (-32602) with `structuredContent.error` — assert that code, not `ValidationError`, in tests.
|
|
@@ -240,6 +241,7 @@ src/
|
|
|
240
241
|
remote-image.ts # https image fetch into memory for the toolkit loader; stops on ctx.signal
|
|
241
242
|
mcp-server/
|
|
242
243
|
tools/
|
|
244
|
+
asset-source-schema.ts # Shared image `source` / sprite `path` schemas: absolute path or URL only
|
|
243
245
|
device-push.ts # Shared post-render push: preview kept on failure, visibility notice
|
|
244
246
|
finish-schema.ts # Shared `finish` input schema (colors | palette, plus dither)
|
|
245
247
|
tools/definitions/
|
|
@@ -257,6 +259,8 @@ src/
|
|
|
257
259
|
pixoo-design-guide.resource.ts
|
|
258
260
|
tests/
|
|
259
261
|
index.session-mode.test.ts # Boots the entry point over HTTP, pins the declared session mode
|
|
262
|
+
config/ # Server config parsed from env vars
|
|
263
|
+
helpers/ # Shared fixtures and assertions (image sources, canvas ink, device failures)
|
|
260
264
|
renderer/ # Pure renderer unit tests (no device)
|
|
261
265
|
resources/ # Resource handler tests
|
|
262
266
|
services/pixoo/ # PixooService tests with a fake client
|
|
@@ -378,7 +382,7 @@ Each per-version file opens with YAML frontmatter: `summary` (required, ≤350 c
|
|
|
378
382
|
## Checklist
|
|
379
383
|
|
|
380
384
|
- [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()`)
|
|
381
|
-
- [ ] Tool inputs are
|
|
385
|
+
- [ ] Tool inputs are closed at every level — the framework closes the root `z.object`, and every nested input object is a `z.strictObject`, so an undeclared key fails by its full path (`Unknown key style.colour. style accepts: …`) instead of being dropped. A tool with nested input objects gets a schema test that walks its `input` and fails on any open object below the root (`tests/helpers/zod-object-nodes.ts`). Use `z.looseObject()` / `.catchall()` only where an open object is genuinely required
|
|
382
386
|
- [ ] JSDoc `@fileoverview` + `@module` on every file
|
|
383
387
|
- [ ] `ctx.log` for logging, `ctx.state` for storage
|
|
384
388
|
- [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
|
package/CLAUDE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** pixoo-mcp-server
|
|
4
|
-
**Version:** 1.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.
|
|
4
|
+
**Version:** 1.3.0
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.10`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.1.0 (protocol revision 2026-07-28 alongside the 2025 era)
|
|
8
8
|
**Zod:** ^4.6.5
|
|
@@ -118,6 +118,7 @@ export const pixooDeviceStatusResource = resource('pixoo://device/status', {
|
|
|
118
118
|
|
|
119
119
|
```ts
|
|
120
120
|
// src/config/server-config.ts
|
|
121
|
+
import * as path from 'node:path';
|
|
121
122
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
122
123
|
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
123
124
|
|
|
@@ -126,7 +127,8 @@ const ServerConfigSchema = z.object({
|
|
|
126
127
|
pixooSize: z.coerce.number()
|
|
127
128
|
.refine((v) => v === 16 || v === 32 || v === 64)
|
|
128
129
|
.default(64).describe('Display size in pixels (16, 32, or 64).'),
|
|
129
|
-
pixooOutputDir: z.string().
|
|
130
|
+
pixooOutputDir: z.string().transform((dir) => path.resolve(dir)).optional()
|
|
131
|
+
.describe('Auto-save directory for preview PNG/GIF files; a relative path resolves against the launch directory.'),
|
|
130
132
|
pixooPushMinIntervalMs: z.coerce.number().int().min(0).default(1000)
|
|
131
133
|
.describe('Pacing floor between device pushes in milliseconds.'),
|
|
132
134
|
});
|
|
@@ -164,12 +166,11 @@ Handlers receive a unified `ctx` object. Key properties used by this server:
|
|
|
164
166
|
|:---------|:------------|
|
|
165
167
|
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
|
|
166
168
|
| `ctx.enrich` | Success-path agent context — `.notice()` / `.total()` / `.echo()` / `.truncated()`. Lands only when the definition declares an `enrichment` block. |
|
|
167
|
-
| `ctx.fail` | Typed throw against the definition's `errors[]` reason union — auto-populates `data.reason`. |
|
|
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.fail` | Typed throw against the definition's `errors[]` reason union — auto-populates `data.reason`; the framework fills in the entry's `recovery` as `data.recovery.hint`. |
|
|
169
170
|
| `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
171
|
| `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
172
|
| `ctx.signal` | `AbortSignal` for cancellation. Pass it to any long-running I/O. |
|
|
172
|
-
| `ctx.requestId` |
|
|
173
|
+
| `ctx.requestId` | Request ID — the one every log record of the call carries and its error envelope returns as `data.requestId`. |
|
|
173
174
|
| `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
|
|
174
175
|
|
|
175
176
|
---
|
|
@@ -192,7 +193,7 @@ Pixoo-specific error reasons declared on tools:
|
|
|
192
193
|
| `unknown_icon` | `InvalidParams` | Icon name not in registry | — |
|
|
193
194
|
| `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable | `true` |
|
|
194
195
|
|
|
195
|
-
`PixooService` and `src/renderer/` raise the device, configuration, and asset reasons themselves (a factory error carrying `data.reason`), so those entries carry `thrownBy: 'service'` — lint-only metadata that keeps `error-contract-unthrown` from reading them as dead. A reason the handler throws with `ctx.fail` stays unmarked
|
|
196
|
+
`PixooService` and `src/renderer/` raise the device, configuration, and asset reasons themselves (a factory error carrying `data.reason`), so those entries carry `thrownBy: 'service'` — lint-only metadata that keeps `error-contract-unthrown` from reading them as dead. A reason the handler throws with `ctx.fail` stays unmarked. Either way, the framework puts the entry's `recovery` on the wire when a failure carrying that `reason` arrives without a hint — a bare `ctx.fail('reason', message)` or a service throw with `data: { reason }` — as `data.recovery.hint`, mirrored into `content[]` unless the message already contains it. Pass an explicit `{ recovery: { hint } }` only to override it for one occurrence — a throw-site hint wins over the contract, so wording that holds for every occurrence of a reason lives in its `errors[]` entry, not at the throw. Every error envelope also carries `data.requestId`, and `content[]` closes with `(reason … · request <id>)`.
|
|
196
197
|
|
|
197
198
|
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.
|
|
198
199
|
|
|
@@ -212,7 +213,7 @@ errors: [
|
|
|
212
213
|
],
|
|
213
214
|
|
|
214
215
|
// in the handler
|
|
215
|
-
throw ctx.fail('invalid_color', `Invalid color "${input.color}"
|
|
216
|
+
throw ctx.fail('invalid_color', `Invalid color "${input.color}"`);
|
|
216
217
|
```
|
|
217
218
|
|
|
218
219
|
Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring. A tool argument that fails the input schema reaches the client as `InvalidParams` (-32602) with `structuredContent.error` — assert that code, not `ValidationError`, in tests.
|
|
@@ -240,6 +241,7 @@ src/
|
|
|
240
241
|
remote-image.ts # https image fetch into memory for the toolkit loader; stops on ctx.signal
|
|
241
242
|
mcp-server/
|
|
242
243
|
tools/
|
|
244
|
+
asset-source-schema.ts # Shared image `source` / sprite `path` schemas: absolute path or URL only
|
|
243
245
|
device-push.ts # Shared post-render push: preview kept on failure, visibility notice
|
|
244
246
|
finish-schema.ts # Shared `finish` input schema (colors | palette, plus dither)
|
|
245
247
|
tools/definitions/
|
|
@@ -257,6 +259,8 @@ src/
|
|
|
257
259
|
pixoo-design-guide.resource.ts
|
|
258
260
|
tests/
|
|
259
261
|
index.session-mode.test.ts # Boots the entry point over HTTP, pins the declared session mode
|
|
262
|
+
config/ # Server config parsed from env vars
|
|
263
|
+
helpers/ # Shared fixtures and assertions (image sources, canvas ink, device failures)
|
|
260
264
|
renderer/ # Pure renderer unit tests (no device)
|
|
261
265
|
resources/ # Resource handler tests
|
|
262
266
|
services/pixoo/ # PixooService tests with a fake client
|
|
@@ -378,7 +382,7 @@ Each per-version file opens with YAML frontmatter: `summary` (required, ≤350 c
|
|
|
378
382
|
## Checklist
|
|
379
383
|
|
|
380
384
|
- [ ] Zod schemas: all fields have `.describe()`, only JSON-Schema-serializable types (no `z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()`)
|
|
381
|
-
- [ ] Tool inputs are
|
|
385
|
+
- [ ] Tool inputs are closed at every level — the framework closes the root `z.object`, and every nested input object is a `z.strictObject`, so an undeclared key fails by its full path (`Unknown key style.colour. style accepts: …`) instead of being dropped. A tool with nested input objects gets a schema test that walks its `input` and fails on any open object below the root (`tests/helpers/zod-object-nodes.ts`). Use `z.looseObject()` / `.catchall()` only where an open object is genuinely required
|
|
382
386
|
- [ ] JSDoc `@fileoverview` + `@module` on every file
|
|
383
387
|
- [ ] `ctx.log` for logging, `ctx.state` for storage
|
|
384
388
|
- [ ] Handlers throw on failure — error factories or plain `Error`, no try/catch
|
package/Dockerfile
CHANGED
|
@@ -29,80 +29,97 @@ RUN bun run build
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
# ==============================================================================
|
|
32
|
-
# Production Stage
|
|
32
|
+
# Production Dependencies Stage
|
|
33
33
|
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
34
|
+
# Installs the production dependency tree for the target platform. Every step
|
|
35
|
+
# here can run JavaScript — bunfig.toml's security scanner runs as a Bun
|
|
36
|
+
# program, and so does the OTel script — so the stage runs on $BUILDPLATFORM
|
|
37
|
+
# and cross-installs with `--os`/`--cpu`, which pick each platform-specific
|
|
38
|
+
# optional dependency (sharp's native `@img/*` bindings) for the target. Only
|
|
39
|
+
# `node_modules` leaves this stage.
|
|
40
|
+
#
|
|
41
|
+
# A clean image rather than `FROM build`: the build stage's node_modules holds
|
|
42
|
+
# devDependencies.
|
|
37
43
|
# ==============================================================================
|
|
38
|
-
FROM oven/bun:1.4.2
|
|
44
|
+
FROM --platform=$BUILDPLATFORM oven/bun:1.4.2 AS deps
|
|
39
45
|
|
|
40
46
|
WORKDIR /usr/src/app
|
|
41
47
|
|
|
42
|
-
# Set the environment to production for performance and to ensure only
|
|
43
|
-
# production dependencies are installed.
|
|
44
|
-
ENV NODE_ENV=production
|
|
45
|
-
|
|
46
|
-
# OCI image metadata (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
|
|
47
|
-
ARG APP_VERSION
|
|
48
|
-
LABEL org.opencontainers.image.title="pixoo-mcp-server"
|
|
49
|
-
LABEL org.opencontainers.image.description="Render and push styled pixel art, text, dashboards, and animations to Divoom Pixoo LED displays."
|
|
50
|
-
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
|
51
|
-
LABEL org.opencontainers.image.version="${APP_VERSION}"
|
|
52
|
-
LABEL org.opencontainers.image.source="https://github.com/cyanheads/pixoo-mcp-server"
|
|
53
|
-
|
|
54
48
|
# Copy dependency manifests. `bunfig.toml` rides along so every install below
|
|
55
49
|
# passes its release-age gate and security scanner, as a local install does.
|
|
56
50
|
COPY package.json bun.lock bunfig.toml ./
|
|
57
51
|
|
|
58
52
|
# The scanner bunfig.toml names is a devDependency, and Bun installs a missing
|
|
59
53
|
# 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
|
-
#
|
|
54
|
+
# aborts. Seed it from the build stage's full install instead. Remove this line,
|
|
55
|
+
# and the `rm` at the end of this stage, if bunfig.toml stops naming a scanner.
|
|
62
56
|
COPY --from=build /usr/src/app/node_modules/@socketsecurity/bun-security-scanner ./node_modules/@socketsecurity/bun-security-scanner
|
|
63
57
|
|
|
58
|
+
# Docker names the target architecture `amd64`/`arm64`; Bun's `--cpu` takes
|
|
59
|
+
# `x64`/`arm64`. Mapped once here, read by both installs below. `oven/bun`
|
|
60
|
+
# publishes only these two architectures, so any other target fails here.
|
|
61
|
+
ARG TARGETOS
|
|
62
|
+
ARG TARGETARCH
|
|
63
|
+
RUN case "$TARGETARCH" in \
|
|
64
|
+
amd64) echo x64 ;; \
|
|
65
|
+
arm64) echo arm64 ;; \
|
|
66
|
+
*) echo "Unsupported TARGETARCH '$TARGETARCH': expected amd64 or arm64" >&2; exit 1 ;; \
|
|
67
|
+
esac > .bun-cpu
|
|
68
|
+
|
|
64
69
|
# Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
|
|
65
70
|
# that are not needed in the final production image.
|
|
66
71
|
# `--omit=peer` drops the framework's optional peer tiers (test runner, service
|
|
67
72
|
# SDKs, parsers) that Bun would otherwise auto-install. Anything this server
|
|
68
73
|
# actually imports belongs in its own `dependencies`, so nothing needed at
|
|
69
|
-
# runtime is lost.
|
|
70
|
-
# install re-resolves the graph and pulls every optional peer back in.
|
|
74
|
+
# runtime is lost.
|
|
71
75
|
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
72
|
-
bun install --production --omit=peer --frozen-lockfile --ignore-scripts
|
|
76
|
+
bun install --production --omit=peer --frozen-lockfile --ignore-scripts \
|
|
77
|
+
--os="$TARGETOS" --cpu="$(cat .bun-cpu)"
|
|
73
78
|
|
|
74
79
|
# Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
|
|
75
80
|
# Installed by default. Omit them for a leaner image at build time
|
|
76
81
|
# with: docker build --build-arg OTEL_ENABLED=false
|
|
77
|
-
#
|
|
78
|
-
# `peerDependencies
|
|
79
|
-
|
|
82
|
+
# The script reads the list and each range from the installed framework's
|
|
83
|
+
# `peerDependencies` and passes the target flags on to its `bun install`.
|
|
84
|
+
COPY scripts/install-otel.ts ./scripts/
|
|
80
85
|
ARG OTEL_ENABLED=true
|
|
81
86
|
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
82
87
|
if [ "$OTEL_ENABLED" = "true" ]; then \
|
|
83
|
-
|
|
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 \
|
|
93
|
-
@opentelemetry/exporter-metrics-otlp-http \
|
|
94
|
-
@opentelemetry/exporter-trace-otlp-http \
|
|
95
|
-
@opentelemetry/instrumentation-http \
|
|
96
|
-
@opentelemetry/instrumentation-pino \
|
|
97
|
-
@opentelemetry/resources \
|
|
98
|
-
@opentelemetry/sdk-logs \
|
|
99
|
-
@opentelemetry/sdk-metrics \
|
|
100
|
-
@opentelemetry/sdk-node \
|
|
101
|
-
@opentelemetry/sdk-trace-node \
|
|
102
|
-
@opentelemetry/semantic-conventions) \
|
|
103
|
-
&& bun add --omit=dev --omit=peer --ignore-scripts $specs; \
|
|
88
|
+
bun scripts/install-otel.ts --os="$TARGETOS" --cpu="$(cat .bun-cpu)"; \
|
|
104
89
|
fi
|
|
105
90
|
|
|
91
|
+
# The seeded scanner served only the installs above; keep it out of the image.
|
|
92
|
+
RUN rm -rf node_modules/@socketsecurity/bun-security-scanner
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ==============================================================================
|
|
96
|
+
# Production Stage
|
|
97
|
+
#
|
|
98
|
+
# This stage creates a minimal, optimized, and secure image for running the
|
|
99
|
+
# application. It uses a slim base image and only includes production
|
|
100
|
+
# dependencies and build artifacts. Its only Bun invocations are HEALTHCHECK
|
|
101
|
+
# and CMD, which run on the real target at container start.
|
|
102
|
+
# ==============================================================================
|
|
103
|
+
FROM oven/bun:1.4.2-slim AS production
|
|
104
|
+
|
|
105
|
+
WORKDIR /usr/src/app
|
|
106
|
+
|
|
107
|
+
# Set the environment to production for performance.
|
|
108
|
+
ENV NODE_ENV=production
|
|
109
|
+
|
|
110
|
+
# OCI image metadata (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
|
|
111
|
+
ARG APP_VERSION
|
|
112
|
+
LABEL org.opencontainers.image.title="pixoo-mcp-server"
|
|
113
|
+
LABEL org.opencontainers.image.description="Render and push styled pixel art, text, dashboards, and animations to Divoom Pixoo LED displays."
|
|
114
|
+
LABEL org.opencontainers.image.licenses="Apache-2.0"
|
|
115
|
+
LABEL org.opencontainers.image.version="${APP_VERSION}"
|
|
116
|
+
LABEL org.opencontainers.image.source="https://github.com/cyanheads/pixoo-mcp-server"
|
|
117
|
+
|
|
118
|
+
# The manifest comes from the build context: the deps stage's copy was rewritten
|
|
119
|
+
# by the OTel install, and the runtime reads only its name, version, and type.
|
|
120
|
+
COPY package.json ./
|
|
121
|
+
COPY --from=deps /usr/src/app/node_modules ./node_modules
|
|
122
|
+
|
|
106
123
|
# Copy the compiled application code from the build stage
|
|
107
124
|
COPY --from=build /usr/src/app/dist ./dist
|
|
108
125
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/pixoo-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/pixoo-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -53,8 +53,8 @@ Tools cover the same ground for tool-only clients: `pixoo_control_device` reads
|
|
|
53
53
|
### `pixoo_display_text` <sub>tool</sub>
|
|
54
54
|
|
|
55
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 `° ← ↑ → ↓ ▲ ▼ ♥ ·
|
|
57
|
-
- `layout[]` reports every fit decision as an `action` (`
|
|
56
|
+
- `font`: `standard` (5×7) and `compact` (3×5) draw printable ASCII plus `° ← ↑ → ↓ ▲ ▼ ♥ · …`, and any other character (`€`, `’`, a newline inside one string) as `?`, named with its code point and line index in the response `notice`; `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` (`shrunk-to-compact` when the text fits only in the compact font, `scrolling` when the returned frames scroll, `none` otherwise), the `font` used, and whether each line's box `fits` on the panel; a single line too wide for the panel starts at x 0, cut at the right edge when it does not scroll. 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
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
|
|
59
59
|
|
|
60
60
|
---
|
|
@@ -63,8 +63,8 @@ Tools cover the same ground for tool-only clients: `pixoo_control_device` reads
|
|
|
63
63
|
|
|
64
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
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
|
|
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. Elements are placed as given and never refit, so `action` is always `none`. 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 element takes `effect` or `animate`, not both. An effect's `amplitude` sets the movement of `float`, `scroll-*`, and `drift`, and the 0–1 depth of the `pulse` and `twinkle` opacity dip
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -144,7 +144,7 @@ Pixoo-specific:
|
|
|
144
144
|
Agent-friendly output:
|
|
145
145
|
|
|
146
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,
|
|
147
|
+
- Layout transparency: `layout[]` reports every renderer decision (font fallback, scrolling, and whether each box fits on the panel) so agents can refine a design
|
|
148
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
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)
|
|
150
150
|
|
|
@@ -252,7 +252,7 @@ cp .env.example .env
|
|
|
252
252
|
|:---|:---|:---|
|
|
253
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. | — |
|
|
254
254
|
| `PIXOO_SIZE` | Display size in pixels: `16`, `32`, or `64`. | `64` |
|
|
255
|
-
| `PIXOO_OUTPUT_DIR` | Directory where render tools save preview PNG and GIF files. Unset, previews are returned only in the response. | — |
|
|
255
|
+
| `PIXOO_OUTPUT_DIR` | Directory where render tools save preview PNG and GIF files. A relative path resolves against the directory the server was launched from, so the saved paths it reports are absolute. Unset, previews are returned only in the response. | — |
|
|
256
256
|
| `PIXOO_PUSH_MIN_INTERVAL_MS` | Minimum gap between device pushes, in ms. | `1000` |
|
|
257
257
|
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
258
258
|
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Stricter tool inputs: unknown nested keys, relative image paths, and effect with animate now fail -32602 instead of being dropped or resolved silently. layout[].action reports only what the frames do, standard and compact text names the characters it draws as ?, and errors carry the request ID."
|
|
3
|
+
breaking: true
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 1.3.0 — 2026-09-26
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **Fallback-glyph `notice` on `pixoo_display_text` and `pixoo_compose_scene`** ([#57](https://github.com/cyanheads/pixoo-mcp-server/issues/57)). A `standard` or `compact` line, scene `text` element, or `progress` `label` holding a character those fonts draw as `?` names it by element and code point (`element 1 "×" (U+00D7)`); the render is unchanged.
|
|
12
|
+
|
|
13
|
+
## Changed
|
|
14
|
+
|
|
15
|
+
- **Image `source` and sprite `path` must be an absolute local path or a URL** ([#53](https://github.com/cyanheads/pixoo-mcp-server/issues/53)). A relative or `~` path, a `file://` URL, or `""` now fails `-32602` `invalid_arguments`: `source: Must be an absolute local path or an https URL, not a relative path, a ~ path, or a file:// URL.` (in a scene, `elements.<i>.source` / `elements.<i>.path`).
|
|
16
|
+
- **Every nested `pixoo_compose_scene` input object rejects unknown keys** ([#54](https://github.com/cyanheads/pixoo-mcp-server/issues/54)) — elements, `style` and its palette stop, `effect`, `animate`, rect `gradient`, `pixels` points, `background` — failing `-32602` by full path: `Unknown key elements.0.colour. elements.0 accepts: type, x, y, w, h, color, …`.
|
|
17
|
+
- **`animate` takes only the tracks an element drives** (#54): an unknown track fails as `Unknown key elements.0.animate.y. …`, and `bitmap`, `progress`, `image`, and `sprite` reject a `color` track.
|
|
18
|
+
- **A compose `background` object holds exactly one of `gradient` or `theme`** (#54). Both or neither fails `background: Set exactly one of gradient or theme.` instead of the gradient winning.
|
|
19
|
+
- **A scene element setting both `effect` and `animate` fails** ([#59](https://github.com/cyanheads/pixoo-mcp-server/issues/59)) with `elements.<i>.effect: Set either effect or animate on an element, not both: …`, instead of dropping the effect.
|
|
20
|
+
- **`pixoo_display_text` rejects unknown keys in `style`, its palette stop, `position`, and `background`** ([#60](https://github.com/cyanheads/pixoo-mcp-server/issues/60)): `Unknown key style.colour. style accepts: palette, shadow, outline, scale, color.`
|
|
21
|
+
- **`layout[].action` reports what the returned frames do** ([#56](https://github.com/cyanheads/pixoo-mcp-server/issues/56)). A `pixoo_display_text` line too wide to fit that does not scroll reports `none` with `fits: false`, not `scrolling`; the output schemas advertise `none`/`shrunk-to-compact`/`scrolling` (display text) and `none` (compose), and `element` is always a number.
|
|
22
|
+
- **Error results carry the request ID** (framework): `data.requestId`, with the text closing `(reason … · request <id>)`.
|
|
23
|
+
- **Input-validation rejections log at `notice`** instead of `error` (framework), tagged `mcp.error.severity: "notice"` on `mcp.errors.classified`.
|
|
24
|
+
|
|
25
|
+
## Fixed
|
|
26
|
+
|
|
27
|
+
- **A relative `PIXOO_OUTPUT_DIR` reports absolute `outputFiles`** ([#61](https://github.com/cyanheads/pixoo-mcp-server/issues/61)); it resolves against the launch directory when the config is read, so files land where they did.
|
|
28
|
+
- **Image URL schemes are read case-insensitively** (#53), so an `HTTPS://…` source is fetched rather than read as a local path.
|
|
29
|
+
- **`pixoo_discover_devices` declares the `discovery_failed` recovery hint callers receive**; the wording on the wire is unchanged.
|
|
30
|
+
- **Multi-arch Docker builds no longer run Bun under emulation**: production dependencies cross-install in a build-platform `deps` stage, and `lint:packaging` fails a target-platform stage that runs JavaScript.
|
|
31
|
+
|
|
32
|
+
## Dependencies
|
|
33
|
+
|
|
34
|
+
- `@cyanheads/mcp-ts-core` `^0.13.9` → `^0.13.10`
|
|
35
|
+
- `hono` `4.13.8` → `4.13.9` (transitive, via the framework)
|
|
@@ -6,7 +6,7 @@ import { z } from '@cyanheads/mcp-ts-core';
|
|
|
6
6
|
declare const ServerConfigSchema: z.ZodObject<{
|
|
7
7
|
pixooIp: z.ZodOptional<z.ZodString>;
|
|
8
8
|
pixooSize: z.ZodDefault<z.ZodCoercedNumber<unknown> & z.ZodType<16 | 32 | 64, unknown, z.core.$ZodTypeInternals<16 | 32 | 64, unknown>>>;
|
|
9
|
-
pixooOutputDir: z.ZodOptional<z.ZodString
|
|
9
|
+
pixooOutputDir: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
10
10
|
pixooPushMinIntervalMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
11
11
|
}, z.core.$strip>;
|
|
12
12
|
export type ServerConfig = z.infer<typeof ServerConfigSchema>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;iBAsBtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED,yCAAyC;AACzC,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @fileoverview Server-specific environment variable configuration for pixoo-mcp-server.
|
|
3
3
|
* @module config/server-config
|
|
4
4
|
*/
|
|
5
|
+
import * as path from 'node:path';
|
|
5
6
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
6
7
|
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
7
8
|
const ServerConfigSchema = z.object({
|
|
@@ -13,7 +14,11 @@ const ServerConfigSchema = z.object({
|
|
|
13
14
|
})
|
|
14
15
|
.default(64)
|
|
15
16
|
.describe('Display size in pixels (16, 32, or 64).'),
|
|
16
|
-
pixooOutputDir: z
|
|
17
|
+
pixooOutputDir: z
|
|
18
|
+
.string()
|
|
19
|
+
.transform((dir) => path.resolve(dir))
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Auto-save directory for preview PNG/GIF files. A relative path resolves against the working directory the server was launched from, so every saved path it reports is absolute.'),
|
|
17
22
|
pixooPushMinIntervalMs: z.coerce
|
|
18
23
|
.number()
|
|
19
24
|
.int()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC1E,SAAS,EAAE,CAAC,CAAC,MAAM;SAChB,MAAM,EAAE;SACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE;QAC/C,OAAO,EAAE,uBAAuB;KACjC,CAAC;SACD,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,yCAAyC,CAAC;IACtD,cAAc,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"server-config.js","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC1E,SAAS,EAAE,CAAC,CAAC,MAAM;SAChB,MAAM,EAAE;SACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE;QAC/C,OAAO,EAAE,uBAAuB;KACjC,CAAC;SACD,OAAO,CAAC,EAAE,CAAC;SACX,QAAQ,CAAC,yCAAyC,CAAC;IACtD,cAAc,EAAE,CAAC;SACd,MAAM,EAAE;SACR,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;SACrC,QAAQ,EAAE;SACV,QAAQ,CACP,iLAAiL,CAClL;IACH,sBAAsB,EAAE,CAAC,CAAC,MAAM;SAC7B,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CAAC,qDAAqD,CAAC;CACnE,CAAC,CAAC;AAIH,IAAI,OAAiC,CAAC;AAEtC,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,cAAc,CAAC,kBAAkB,EAAE;QAC7C,OAAO,EAAE,UAAU;QACnB,SAAS,EAAE,YAAY;QACvB,cAAc,EAAE,kBAAkB;QAClC,sBAAsB,EAAE,4BAA4B;KACrD,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,iBAAiB;IAC/B,OAAO,GAAG,SAAS,CAAC;AACtB,CAAC"}
|
|
@@ -15,7 +15,7 @@ At typical viewing distance for a 64×64 LED display:
|
|
|
15
15
|
- **Font choices:**
|
|
16
16
|
- Standard (5×7): default for all text. Readable at 1× scale. Excellent at 2× (14px tall = chunky block letters)
|
|
17
17
|
- Compact (3×5): secondary text, captions, labels when space is tight
|
|
18
|
-
- Standard and compact both draw printable ASCII plus \`° ← ↑ → ↓ ▲ ▼ ♥ · …\` for units, trends, and separators
|
|
18
|
+
- Standard and compact both draw printable ASCII plus \`° ← ↑ → ↓ ▲ ▼ ♥ · …\` for units, trends, and separators. Any other character (\`€\`, \`×\`, \`’\`, \`—\`, a newline inside one string, the invisible U+FE0F after \`♥\`) draws as \`?\`, and the response notice names it with its code point and element index
|
|
19
19
|
- Numerals (11×18): clocks and hero readouts. Draws 0–9, space, and \`: . - + / % ° ?\` only, each digit on one 13px advance so a changing value keeps its width. Auto-fit never picks it; text holding any other character is rejected, so units and labels go in their own standard or compact text element
|
|
20
20
|
- Scale ≥2: decorative or hero use only — verify fit before pushing
|
|
21
21
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Inputs that name an image asset: the image `source` of pixoo_push_image
|
|
3
|
+
* and of a scene `image` element, and a scene `sprite` element's `path`. Each must be a
|
|
4
|
+
* URL or an absolute local path — a relative path would resolve against the server's
|
|
5
|
+
* working directory, which a caller cannot see.
|
|
6
|
+
* @module mcp-server/tools/asset-source-schema
|
|
7
|
+
*/
|
|
8
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
9
|
+
/** An image source: an absolute local path or an https URL. */
|
|
10
|
+
export declare const ImageSourceSchema: z.ZodString;
|
|
11
|
+
/** A sprite sheet path: an absolute local path. */
|
|
12
|
+
export declare const SpritePathSchema: z.ZodString;
|
|
13
|
+
//# sourceMappingURL=asset-source-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-source-schema.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/asset-source-schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAW3C,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,aAG5B,CAAC;AAEH,mDAAmD;AACnD,eAAO,MAAM,gBAAgB,aAE3B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Inputs that name an image asset: the image `source` of pixoo_push_image
|
|
3
|
+
* and of a scene `image` element, and a scene `sprite` element's `path`. Each must be a
|
|
4
|
+
* URL or an absolute local path — a relative path would resolve against the server's
|
|
5
|
+
* working directory, which a caller cannot see.
|
|
6
|
+
* @module mcp-server/tools/asset-source-schema
|
|
7
|
+
*/
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { z } from '@cyanheads/mcp-ts-core';
|
|
10
|
+
import { isRemoteSource } from '../../renderer/remote-image.js';
|
|
11
|
+
/**
|
|
12
|
+
* True for a value that names the same asset whatever the server's working directory.
|
|
13
|
+
* An http URL passes: the loaders refuse it as `asset_not_found` with a message naming
|
|
14
|
+
* the https requirement, as they refuse a sprite path given as any URL.
|
|
15
|
+
*/
|
|
16
|
+
const namesAssetAbsolutely = (value) => isRemoteSource(value) || path.isAbsolute(value);
|
|
17
|
+
/** An image source: an absolute local path or an https URL. */
|
|
18
|
+
export const ImageSourceSchema = z.string().refine(namesAssetAbsolutely, {
|
|
19
|
+
error: 'Must be an absolute local path or an https URL, not a relative path, a ~ path, or a file:// URL.',
|
|
20
|
+
});
|
|
21
|
+
/** A sprite sheet path: an absolute local path. */
|
|
22
|
+
export const SpritePathSchema = z.string().refine(namesAssetAbsolutely, {
|
|
23
|
+
error: 'Must be an absolute local path, not a relative path, a ~ path, or a file:// URL.',
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=asset-source-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-source-schema.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/asset-source-schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAW,EAAE,CACtD,cAAc,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAElD,+DAA+D;AAC/D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,EAAE;IACvE,KAAK,EACH,kGAAkG;CACrG,CAAC,CAAC;AAEH,mDAAmD;AACnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,oBAAoB,EAAE;IACtE,KAAK,EAAE,kFAAkF;CAC1F,CAAC,CAAC"}
|