@cyanheads/pixoo-mcp-server 1.1.1 → 1.1.3

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 (35) hide show
  1. package/AGENTS.md +93 -36
  2. package/CLAUDE.md +93 -36
  3. package/README.md +100 -71
  4. package/changelog/1.1.x/1.1.2.md +36 -0
  5. package/changelog/1.1.x/1.1.3.md +30 -0
  6. package/changelog/template.md +9 -26
  7. package/dist/index.js +3 -3
  8. package/dist/index.js.map +1 -1
  9. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts +5 -1
  10. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.d.ts.map +1 -1
  11. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js +9 -6
  12. package/dist/mcp-server/tools/definitions/pixoo-compose-scene.tool.js.map +1 -1
  13. package/dist/mcp-server/tools/definitions/pixoo-control-device.tool.d.ts +1 -11
  14. package/dist/mcp-server/tools/definitions/pixoo-control-device.tool.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/pixoo-control-device.tool.js +1 -13
  16. package/dist/mcp-server/tools/definitions/pixoo-control-device.tool.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/pixoo-design-brief.tool.js +1 -1
  18. package/dist/mcp-server/tools/definitions/pixoo-discover-devices.tool.d.ts +1 -0
  19. package/dist/mcp-server/tools/definitions/pixoo-discover-devices.tool.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/pixoo-discover-devices.tool.js +1 -0
  21. package/dist/mcp-server/tools/definitions/pixoo-discover-devices.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts +4 -1
  23. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js +5 -2
  25. package/dist/mcp-server/tools/definitions/pixoo-display-text.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.d.ts +2 -1
  27. package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.d.ts.map +1 -1
  28. package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.js +9 -6
  29. package/dist/mcp-server/tools/definitions/pixoo-overlay-text.tool.js.map +1 -1
  30. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts +3 -0
  31. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.d.ts.map +1 -1
  32. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js +4 -1
  33. package/dist/mcp-server/tools/definitions/pixoo-push-image.tool.js.map +1 -1
  34. package/package.json +11 -10
  35. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** pixoo-mcp-server
4
- **Version:** 1.1.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 1.1.3
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revision 2026-07-28 alongside the 2025 era)
8
- **Zod:** ^4.4.3
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.
11
11
 
@@ -28,13 +28,15 @@ Tools call renderer + service; they don't talk to the toolkit directly.
28
28
 
29
29
  When the user asks what's next or needs direction, suggest options based on the current project state. Common next steps:
30
30
 
31
- 1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date
32
- 2. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-resource`, `add-prompt` skills
33
- 3. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill
34
- 4. **Run `devcheck`** — lint, format, typecheck, and security audit
35
- 5. **Run the `security-pass` skill** — audit handlers for MCP-specific security gaps: output injection, scope blast radius, input sinks, tenant isolation
36
- 6. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
37
- 7. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
31
+ 1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date with the current codebase
32
+ 2. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt` skills
33
+ 3. **Add services** — scaffold domain service integrations using the `add-service` skill
34
+ 4. **Add tests** — scaffold tests for existing definitions using the `add-test` skill
35
+ 5. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill, get a report of issues and pain points
36
+ 6. **Run `devcheck`** — lint, format, typecheck, and security audit
37
+ 7. **Run the `security-pass` skill** — audit handlers for MCP-specific security gaps: output injection, scope blast radius, input sinks, tenant isolation
38
+ 8. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
39
+ 9. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
38
40
 
39
41
  Tailor suggestions to what's actually missing or stale — don't recite the full list every time.
40
42
 
@@ -45,11 +47,12 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
45
47
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `serviceUnavailable()`, etc.) when the error code matters.
46
48
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
47
49
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
48
- - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler. (`ctx.elicit` was removed in the SDK v2 migration.)
50
+ - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler. (`ctx.elicit` was removed in the SDK v2 migration.) The server declares `sessionMode: 'stateless'` because no handler does this today — the first one that does changes it to `{ default: 'stateful', require: 'stateful' }` in `src/index.ts`, `.env.example`, the Dockerfile, and the README.
49
51
  - **Secrets in env vars only** — never hardcoded.
52
+ - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
50
53
  - **Every `PixooResult` checked.** No fire-and-forget device calls. `pushed: true` means `error_code: 0` from the device.
51
54
  - **Adding an env var requires both files** — `server.json` (`environmentVariables[]`) and `manifest.json` (`mcp_config.env` + `user_config`). `bun run lint:packaging` verifies the names match.
52
- - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped.
55
+ - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
53
56
 
54
57
  ---
55
58
 
@@ -138,6 +141,19 @@ export function getServerConfig() {
138
141
  }
139
142
  ```
140
143
 
144
+ ### Session posture and shutdown
145
+
146
+ ```ts
147
+ await createApp({
148
+ sessionMode: 'stateless',
149
+ setup(core) { initPixooService(core.config, core.storage); },
150
+ });
151
+ ```
152
+
153
+ `sessionMode` declares the HTTP session posture in `src/`. `MCP_SESSION_MODE` still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). Add `require: 'stateful'` when a tool asks the caller for input mid-handler via `ctx.requestInput`: startup then fails with a `ConfigurationError` rather than serving a mode in which a 2025-era client can never answer the prompt. Stdio is never refused.
154
+
155
+ `teardown(core)` is the `setup()` counterpart — release a watcher, socket, or non-`unref()`'d timer there. It runs after the transport stops and before the logger closes, on every shutdown path. This server passes none: `PixooService` opens a `fetch` per device command and holds no persistent handle. Add one if a service starts keeping a socket, watcher, or ref'd timer.
156
+
141
157
  ---
142
158
 
143
159
  ## Context
@@ -149,6 +165,7 @@ Handlers receive a unified `ctx` object. Key properties used by this server:
149
165
  | `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. |
150
166
  | `ctx.enrich` | Success-path agent context — `.notice()` / `.total()` / `.echo()` / `.truncated()`. Lands only when the definition declares an `enrichment` block. |
151
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. |
152
169
  | `ctx.state` | Tenant-scoped KV — `.get`, `.set(key, value, { ttl? })`, `.delete`, `.getMany`, `.list`. Keys are validated; colons are not legal separators. |
153
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. |
154
171
  | `ctx.signal` | `AbortSignal` for cancellation. Pass it to any long-running I/O. |
@@ -174,19 +191,27 @@ Pixoo-specific error reasons declared on tools:
174
191
  | `unknown_icon` | `InvalidParams` | Icon name not in registry |
175
192
  | `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable |
176
193
 
194
+ `PixooService` and `src/renderer/` raise the device, configuration, and remote-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, and every such site forwards its declared recovery with `ctx.recoveryFor`. A computed reason forwards the same way (`ctx.recoveryFor(reason)`); `lint:mcp` skips a definition whose `ctx.fail` reason is non-literal, so a clean lint says nothing about those sites.
195
+
177
196
  ```ts
178
197
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
179
198
 
180
199
  errors: [
181
200
  { reason: 'no_device_configured', code: JsonRpcErrorCode.InvalidParams,
182
- when: 'PIXOO_IP not configured',
183
- recovery: 'Run pixoo_discover_devices to find device IPs, then set PIXOO_IP.' },
184
- { reason: 'device_unreachable', code: JsonRpcErrorCode.ServiceUnavailable,
185
- when: 'Device network timeout or connection refused',
186
- recovery: 'Check the device is on the same LAN and powered on, then retry.' },
201
+ when: 'PIXOO_IP is not set.',
202
+ recovery: 'Run pixoo_discover_devices to find the device IP, then set PIXOO_IP.',
203
+ thrownBy: 'service' },
204
+ { reason: 'invalid_color', code: JsonRpcErrorCode.InvalidParams,
205
+ when: 'The color value could not be resolved.',
206
+ recovery: 'Use a hex color (#RRGGBB or #RGB, with or without the #) or a case-insensitive named color such as white, orange, or claude.' },
187
207
  ],
208
+
209
+ // in the handler
210
+ throw ctx.fail('invalid_color', `Invalid color "${input.color}"`, ctx.recoveryFor('invalid_color'));
188
211
  ```
189
212
 
213
+ 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.
214
+
190
215
  ---
191
216
 
192
217
  ## Structure
@@ -199,16 +224,14 @@ src/
199
224
  services/
200
225
  pixoo/
201
226
  pixoo-service.ts # PixooService — toolkit wrapper, pacing, result mapping
202
- types.ts # Service types
203
227
  renderer/
204
228
  themes.ts # Theme + palette registry
205
229
  icons.ts # Icon registry (SVG path data by category)
206
- styled-text.ts # Gradient ramp + shadow + outline text engine
207
- layout.ts # Semantic positioning resolver
208
- effects.ts # Animation preset → keyframe compiler
209
- keyframes.ts # Keyframe interpolation (lerp numbers/colors, snap booleans)
230
+ text-engine.ts # Gradient ramp + shadow + outline text engine, overflow handling
231
+ scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
232
+ keyframes.ts # Keyframe interpolation + animation preset compiler
210
233
  preview.ts # PNG/contact-sheet/GIF encoding
211
- elements/ # Per-type element renderers
234
+ remote-image.ts # https image fetch to a temp file for the toolkit loader
212
235
  mcp-server/
213
236
  tools/definitions/
214
237
  pixoo-display-text.tool.ts
@@ -224,7 +247,10 @@ src/
224
247
  pixoo-icons.resource.ts
225
248
  pixoo-design-guide.resource.ts
226
249
  tests/
250
+ index.session-mode.test.ts # Boots the entry point over HTTP, pins the declared session mode
227
251
  renderer/ # Pure renderer unit tests (no device)
252
+ resources/ # Resource handler tests
253
+ services/pixoo/ # PixooService tests with a fake client
228
254
  tools/ # Tool handler tests with mock context
229
255
  ```
230
256
 
@@ -243,7 +269,9 @@ tests/
243
269
 
244
270
  ## Skills
245
271
 
246
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches.
272
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, and this server ships `.claude-plugin/` and `.codex-plugin/`, so a root `skills/` would hand these development skills to every agent that installs it.
273
+
274
+ **Agent skill directories:** `.claude/skills/` and `.agents/skills/` carry copies of `framework-skills/`. After framework updates, run the `maintenance` skill — Phase B re-syncs both.
247
275
 
248
276
  Available skills:
249
277
 
@@ -258,24 +286,25 @@ Available skills:
258
286
  | `add-service` | Scaffold a new service integration |
259
287
  | `add-test` | Scaffold test file for a tool, resource, or service |
260
288
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
261
- | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface |
262
- | `security-pass` | Audit server for MCP-flavored security gaps |
263
- | `code-simplifier` | Post-session cleanup against `git diff` |
289
+ | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
290
+ | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
291
+ | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
264
292
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
265
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag |
266
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker |
293
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
294
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
295
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
267
296
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
268
- | `orchestrations` | Chain task skills into a gated multi-phase pipeline when sub-agents are available |
269
- | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping |
297
+ | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
298
+ | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
270
299
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
271
300
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
272
301
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
273
- | `api-canvas` | DataCanvas: register tabular data, run SQL, export — Tier 3 opt-in |
302
+ | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
274
303
  | `api-config` | AppConfig, parseEnvConfig, env vars |
275
304
  | `api-context` | Context interface, RequestContext, logger, state, multi-round-trip input |
276
305
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
277
306
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
278
- | `api-mirror` | MirrorService: self-refreshing local SQLite/FTS5 mirror of a bulk dataset — Tier 3 opt-in |
307
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
279
308
  | `api-services` | LLM, Speech, Graph services |
280
309
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
281
310
  | `api-testing` | createMockContext, createFetchMock, runToolContract, test patterns |
@@ -294,9 +323,10 @@ Available skills:
294
323
  | `bun run rebuild` | Clean + build |
295
324
  | `bun run clean` | Remove build artifacts |
296
325
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
297
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Re-resolves the `^`-ranged framework pin — verify the lock afterwards |
326
+ | `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` |
327
+ | `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` |
298
328
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
299
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity |
329
+ | `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) |
300
330
  | `bun run list-skills` | Print the skill registry |
301
331
  | `bun run tree` | Generate directory structure doc |
302
332
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -308,6 +338,32 @@ Available skills:
308
338
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
309
339
  | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
310
340
 
341
+ **CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL is GitHub-owned end to end, and the file runs only while the repo's CodeQL *default setup* is turned off. Verification — `devcheck`, tests, the release gates — runs locally; don't add a workflow that re-runs it.
342
+
343
+ ---
344
+
345
+ ## Bundling
346
+
347
+ `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.
348
+
349
+ `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`.
350
+
351
+ ---
352
+
353
+ ## Changelog
354
+
355
+ Directory-based, grouped by minor series via the `.x` semver-wildcard convention. Source of truth: `changelog/<major.minor>.x/<version>.md` — one file per release, shipped in the npm package. At release, author the per-version file with a concrete version and date, then run `bun run changelog:build` to regenerate the rollup. `changelog/template.md` is a **pristine format reference** — never edited or moved. `CHANGELOG.md` is a **navigation index** regenerated by `bun run changelog:build` — devcheck hard-fails on drift; never hand-edit it.
356
+
357
+ Each per-version file opens with YAML frontmatter: `summary` (required, ≤350 chars), optional `breaking: true` for changes consumers must act on, optional `security: true` only for a security fix in this server's own source (never a dependency CVE bump — those go under `## Dependencies`).
358
+
359
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries.
360
+
361
+ ---
362
+
363
+ ## Publishing
364
+
365
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-and-publish` then fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
366
+
311
367
  ---
312
368
 
313
369
  ## Checklist
@@ -321,4 +377,5 @@ Available skills:
321
377
  - [ ] Every device call goes through `PixooService`; every `PixooResult` checked
322
378
  - [ ] Renderer functions have no device dependency — testable without hardware
323
379
  - [ ] Env var added? Declared in BOTH `server.json` and `manifest.json` (`lint:packaging` enforces parity)
380
+ - [ ] `.codex-plugin/plugin.json` and `.claude-plugin/plugin.json` carry the `package.json` `version`; display fields use the unscoped repo name `pixoo-mcp-server`. A user-supplied variable goes in `env_vars` (`.codex-plugin/mcp.json`) or `userConfig` + `"${user_config.<option>}"` (`.claude-plugin/plugin.json`) — never `"KEY": ""` in `env`
324
381
  - [ ] `bun run devcheck` and `bun run test` pass
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** pixoo-mcp-server
4
- **Version:** 1.1.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.12.3`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 1.1.3
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revision 2026-07-28 alongside the 2025 era)
8
- **Zod:** ^4.4.3
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.
11
11
 
@@ -28,13 +28,15 @@ Tools call renderer + service; they don't talk to the toolkit directly.
28
28
 
29
29
  When the user asks what's next or needs direction, suggest options based on the current project state. Common next steps:
30
30
 
31
- 1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date
32
- 2. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-resource`, `add-prompt` skills
33
- 3. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill
34
- 4. **Run `devcheck`** — lint, format, typecheck, and security audit
35
- 5. **Run the `security-pass` skill** — audit handlers for MCP-specific security gaps: output injection, scope blast radius, input sinks, tenant isolation
36
- 6. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
37
- 7. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
31
+ 1. **Re-run the `setup` skill** — ensures CLAUDE.md, skills, structure, and metadata are populated and up to date with the current codebase
32
+ 2. **Add tools/resources/prompts** — scaffold new definitions using the `add-tool`, `add-app-tool`, `add-resource`, `add-prompt` skills
33
+ 3. **Add services** — scaffold domain service integrations using the `add-service` skill
34
+ 4. **Add tests** — scaffold tests for existing definitions using the `add-test` skill
35
+ 5. **Field-test definitions** — exercise tools/resources/prompts with real inputs using the `field-test` skill, get a report of issues and pain points
36
+ 6. **Run `devcheck`** — lint, format, typecheck, and security audit
37
+ 7. **Run the `security-pass` skill** — audit handlers for MCP-specific security gaps: output injection, scope blast radius, input sinks, tenant isolation
38
+ 8. **Run the `polish-docs-meta` skill** — finalize README, CHANGELOG, metadata, and agent protocol for shipping
39
+ 9. **Run the `maintenance` skill** — investigate changelogs, adopt upstream changes, and sync skills after `bun update --latest`
38
40
 
39
41
  Tailor suggestions to what's actually missing or stale — don't recite the full list every time.
40
42
 
@@ -45,11 +47,12 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
45
47
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `serviceUnavailable()`, etc.) when the error code matters.
46
48
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
47
49
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
48
- - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler. (`ctx.elicit` was removed in the SDK v2 migration.)
50
+ - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler. (`ctx.elicit` was removed in the SDK v2 migration.) The server declares `sessionMode: 'stateless'` because no handler does this today — the first one that does changes it to `{ default: 'stateful', require: 'stateful' }` in `src/index.ts`, `.env.example`, the Dockerfile, and the README.
49
51
  - **Secrets in env vars only** — never hardcoded.
52
+ - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
50
53
  - **Every `PixooResult` checked.** No fire-and-forget device calls. `pushed: true` means `error_code: 0` from the device.
51
54
  - **Adding an env var requires both files** — `server.json` (`environmentVariables[]`) and `manifest.json` (`mcp_config.env` + `user_config`). `bun run lint:packaging` verifies the names match.
52
- - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped.
55
+ - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
53
56
 
54
57
  ---
55
58
 
@@ -138,6 +141,19 @@ export function getServerConfig() {
138
141
  }
139
142
  ```
140
143
 
144
+ ### Session posture and shutdown
145
+
146
+ ```ts
147
+ await createApp({
148
+ sessionMode: 'stateless',
149
+ setup(core) { initPixooService(core.config, core.storage); },
150
+ });
151
+ ```
152
+
153
+ `sessionMode` declares the HTTP session posture in `src/`. `MCP_SESSION_MODE` still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). Add `require: 'stateful'` when a tool asks the caller for input mid-handler via `ctx.requestInput`: startup then fails with a `ConfigurationError` rather than serving a mode in which a 2025-era client can never answer the prompt. Stdio is never refused.
154
+
155
+ `teardown(core)` is the `setup()` counterpart — release a watcher, socket, or non-`unref()`'d timer there. It runs after the transport stops and before the logger closes, on every shutdown path. This server passes none: `PixooService` opens a `fetch` per device command and holds no persistent handle. Add one if a service starts keeping a socket, watcher, or ref'd timer.
156
+
141
157
  ---
142
158
 
143
159
  ## Context
@@ -149,6 +165,7 @@ Handlers receive a unified `ctx` object. Key properties used by this server:
149
165
  | `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. |
150
166
  | `ctx.enrich` | Success-path agent context — `.notice()` / `.total()` / `.echo()` / `.truncated()`. Lands only when the definition declares an `enrichment` block. |
151
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. |
152
169
  | `ctx.state` | Tenant-scoped KV — `.get`, `.set(key, value, { ttl? })`, `.delete`, `.getMany`, `.list`. Keys are validated; colons are not legal separators. |
153
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. |
154
171
  | `ctx.signal` | `AbortSignal` for cancellation. Pass it to any long-running I/O. |
@@ -174,19 +191,27 @@ Pixoo-specific error reasons declared on tools:
174
191
  | `unknown_icon` | `InvalidParams` | Icon name not in registry |
175
192
  | `discovery_failed` | `ServiceUnavailable` | Divoom cloud unreachable |
176
193
 
194
+ `PixooService` and `src/renderer/` raise the device, configuration, and remote-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, and every such site forwards its declared recovery with `ctx.recoveryFor`. A computed reason forwards the same way (`ctx.recoveryFor(reason)`); `lint:mcp` skips a definition whose `ctx.fail` reason is non-literal, so a clean lint says nothing about those sites.
195
+
177
196
  ```ts
178
197
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
179
198
 
180
199
  errors: [
181
200
  { reason: 'no_device_configured', code: JsonRpcErrorCode.InvalidParams,
182
- when: 'PIXOO_IP not configured',
183
- recovery: 'Run pixoo_discover_devices to find device IPs, then set PIXOO_IP.' },
184
- { reason: 'device_unreachable', code: JsonRpcErrorCode.ServiceUnavailable,
185
- when: 'Device network timeout or connection refused',
186
- recovery: 'Check the device is on the same LAN and powered on, then retry.' },
201
+ when: 'PIXOO_IP is not set.',
202
+ recovery: 'Run pixoo_discover_devices to find the device IP, then set PIXOO_IP.',
203
+ thrownBy: 'service' },
204
+ { reason: 'invalid_color', code: JsonRpcErrorCode.InvalidParams,
205
+ when: 'The color value could not be resolved.',
206
+ recovery: 'Use a hex color (#RRGGBB or #RGB, with or without the #) or a case-insensitive named color such as white, orange, or claude.' },
187
207
  ],
208
+
209
+ // in the handler
210
+ throw ctx.fail('invalid_color', `Invalid color "${input.color}"`, ctx.recoveryFor('invalid_color'));
188
211
  ```
189
212
 
213
+ 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.
214
+
190
215
  ---
191
216
 
192
217
  ## Structure
@@ -199,16 +224,14 @@ src/
199
224
  services/
200
225
  pixoo/
201
226
  pixoo-service.ts # PixooService — toolkit wrapper, pacing, result mapping
202
- types.ts # Service types
203
227
  renderer/
204
228
  themes.ts # Theme + palette registry
205
229
  icons.ts # Icon registry (SVG path data by category)
206
- styled-text.ts # Gradient ramp + shadow + outline text engine
207
- layout.ts # Semantic positioning resolver
208
- effects.ts # Animation preset → keyframe compiler
209
- keyframes.ts # Keyframe interpolation (lerp numbers/colors, snap booleans)
230
+ text-engine.ts # Gradient ramp + shadow + outline text engine, overflow handling
231
+ scene-renderer.ts # Element vocabulary, layout resolver, frame rendering
232
+ keyframes.ts # Keyframe interpolation + animation preset compiler
210
233
  preview.ts # PNG/contact-sheet/GIF encoding
211
- elements/ # Per-type element renderers
234
+ remote-image.ts # https image fetch to a temp file for the toolkit loader
212
235
  mcp-server/
213
236
  tools/definitions/
214
237
  pixoo-display-text.tool.ts
@@ -224,7 +247,10 @@ src/
224
247
  pixoo-icons.resource.ts
225
248
  pixoo-design-guide.resource.ts
226
249
  tests/
250
+ index.session-mode.test.ts # Boots the entry point over HTTP, pins the declared session mode
227
251
  renderer/ # Pure renderer unit tests (no device)
252
+ resources/ # Resource handler tests
253
+ services/pixoo/ # PixooService tests with a fake client
228
254
  tools/ # Tool handler tests with mock context
229
255
  ```
230
256
 
@@ -243,7 +269,9 @@ tests/
243
269
 
244
270
  ## Skills
245
271
 
246
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches.
272
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, and this server ships `.claude-plugin/` and `.codex-plugin/`, so a root `skills/` would hand these development skills to every agent that installs it.
273
+
274
+ **Agent skill directories:** `.claude/skills/` and `.agents/skills/` carry copies of `framework-skills/`. After framework updates, run the `maintenance` skill — Phase B re-syncs both.
247
275
 
248
276
  Available skills:
249
277
 
@@ -258,24 +286,25 @@ Available skills:
258
286
  | `add-service` | Scaffold a new service integration |
259
287
  | `add-test` | Scaffold test file for a tool, resource, or service |
260
288
  | `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
261
- | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface |
262
- | `security-pass` | Audit server for MCP-flavored security gaps |
263
- | `code-simplifier` | Post-session cleanup against `git diff` |
289
+ | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
290
+ | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
291
+ | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
264
292
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
265
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag |
266
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker |
293
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
294
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
295
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
267
296
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
268
- | `orchestrations` | Chain task skills into a gated multi-phase pipeline when sub-agents are available |
269
- | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping |
297
+ | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
298
+ | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
270
299
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
271
300
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
272
301
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
273
- | `api-canvas` | DataCanvas: register tabular data, run SQL, export — Tier 3 opt-in |
302
+ | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
274
303
  | `api-config` | AppConfig, parseEnvConfig, env vars |
275
304
  | `api-context` | Context interface, RequestContext, logger, state, multi-round-trip input |
276
305
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
277
306
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
278
- | `api-mirror` | MirrorService: self-refreshing local SQLite/FTS5 mirror of a bulk dataset — Tier 3 opt-in |
307
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
279
308
  | `api-services` | LLM, Speech, Graph services |
280
309
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
281
310
  | `api-testing` | createMockContext, createFetchMock, runToolContract, test patterns |
@@ -294,9 +323,10 @@ Available skills:
294
323
  | `bun run rebuild` | Clean + build |
295
324
  | `bun run clean` | Remove build artifacts |
296
325
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
297
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Re-resolves the `^`-ranged framework pin — verify the lock afterwards |
326
+ | `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` |
327
+ | `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` |
298
328
  | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
299
- | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity |
329
+ | `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) |
300
330
  | `bun run list-skills` | Print the skill registry |
301
331
  | `bun run tree` | Generate directory structure doc |
302
332
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -308,6 +338,32 @@ Available skills:
308
338
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
309
339
  | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
310
340
 
341
+ **CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL is GitHub-owned end to end, and the file runs only while the repo's CodeQL *default setup* is turned off. Verification — `devcheck`, tests, the release gates — runs locally; don't add a workflow that re-runs it.
342
+
343
+ ---
344
+
345
+ ## Bundling
346
+
347
+ `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.
348
+
349
+ `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`.
350
+
351
+ ---
352
+
353
+ ## Changelog
354
+
355
+ Directory-based, grouped by minor series via the `.x` semver-wildcard convention. Source of truth: `changelog/<major.minor>.x/<version>.md` — one file per release, shipped in the npm package. At release, author the per-version file with a concrete version and date, then run `bun run changelog:build` to regenerate the rollup. `changelog/template.md` is a **pristine format reference** — never edited or moved. `CHANGELOG.md` is a **navigation index** regenerated by `bun run changelog:build` — devcheck hard-fails on drift; never hand-edit it.
356
+
357
+ Each per-version file opens with YAML frontmatter: `summary` (required, ≤350 chars), optional `breaking: true` for changes consumers must act on, optional `security: true` only for a security fix in this server's own source (never a dependency CVE bump — those go under `## Dependencies`).
358
+
359
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries.
360
+
361
+ ---
362
+
363
+ ## Publishing
364
+
365
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-and-publish` then fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
366
+
311
367
  ---
312
368
 
313
369
  ## Checklist
@@ -321,4 +377,5 @@ Available skills:
321
377
  - [ ] Every device call goes through `PixooService`; every `PixooResult` checked
322
378
  - [ ] Renderer functions have no device dependency — testable without hardware
323
379
  - [ ] Env var added? Declared in BOTH `server.json` and `manifest.json` (`lint:packaging` enforces parity)
380
+ - [ ] `.codex-plugin/plugin.json` and `.claude-plugin/plugin.json` carry the `package.json` `version`; display fields use the unscoped repo name `pixoo-mcp-server`. A user-supplied variable goes in `env_vars` (`.codex-plugin/mcp.json`) or `userConfig` + `"${user_config.<option>}"` (`.claude-plugin/plugin.json`) — never `"KEY": ""` in `env`
324
381
  - [ ] `bun run devcheck` and `bun run test` pass