pmx-canvas 0.2.7 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +148 -1
  2. package/Readme.md +13 -11
  3. package/dist/canvas/index.js +2 -2
  4. package/dist/json-render/index.css +1 -1
  5. package/dist/types/cli/daemon.d.ts +74 -0
  6. package/dist/types/cli/watch.d.ts +2 -2
  7. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  8. package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
  9. package/dist/types/client/canvas/Minimap.d.ts +1 -1
  10. package/dist/types/client/nodes/FileNode.d.ts +1 -1
  11. package/dist/types/client/nodes/ImageNode.d.ts +1 -1
  12. package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
  13. package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
  14. package/dist/types/client/nodes/PromptNode.d.ts +1 -1
  15. package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
  16. package/dist/types/mcp/canvas-access.d.ts +0 -7
  17. package/dist/types/server/ax-state-manager.d.ts +3 -2
  18. package/dist/types/server/bundled-skills.d.ts +3 -3
  19. package/dist/types/server/canvas-schema.d.ts +1 -1
  20. package/dist/types/server/canvas-state.d.ts +2 -0
  21. package/dist/types/server/ext-app-lookup.d.ts +1 -3
  22. package/dist/types/server/html-primitives.d.ts +1 -1
  23. package/dist/types/server/index.d.ts +4 -4
  24. package/dist/types/server/operations/composites.d.ts +29 -23
  25. package/dist/types/server/operations/http.d.ts +2 -1
  26. package/dist/types/server/operations/index.d.ts +1 -1
  27. package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
  28. package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
  29. package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
  30. package/dist/types/shared/ax-intent.d.ts +1 -1
  31. package/docs/RELEASE.md +23 -8
  32. package/docs/ax-host-adapter-contract.md +7 -7
  33. package/docs/ax-state-contract.md +7 -6
  34. package/docs/bun-webview-integration.md +5 -5
  35. package/docs/cli.md +1 -1
  36. package/docs/http-api.md +35 -3
  37. package/docs/mcp.md +121 -100
  38. package/docs/node-types.md +45 -35
  39. package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
  40. package/docs/plans/plan-008-registry-finish.md +2 -0
  41. package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
  42. package/docs/screenshot.png +0 -0
  43. package/docs/tech-debt-assessment-2026-06.md +1 -1
  44. package/docs/tech-debt-assessment-2026-07.md +135 -0
  45. package/package.json +5 -2
  46. package/skills/data-analysis/SKILL.md +3 -3
  47. package/skills/frontend-design/SKILL.md +3 -2
  48. package/skills/json-render-mcp/SKILL.md +4 -3
  49. package/skills/playwright-cli/SKILL.md +1 -1
  50. package/skills/pmx-canvas/SKILL.md +12 -7
  51. package/skills/pmx-canvas/evals/evals.json +2 -2
  52. package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
  53. package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
  54. package/skills/pmx-canvas/references/full-reference.md +58 -56
  55. package/skills/pmx-canvas/references/html-primitives.md +3 -3
  56. package/skills/tufte-viz/SKILL.md +5 -4
  57. package/skills/web-artifacts-builder/SKILL.md +4 -4
  58. package/src/cli/agent.ts +1861 -1548
  59. package/src/cli/daemon.ts +460 -0
  60. package/src/cli/index.ts +63 -326
  61. package/src/cli/watch.ts +2 -10
  62. package/src/client/App.tsx +48 -46
  63. package/src/client/canvas/AttentionHistory.tsx +11 -1
  64. package/src/client/canvas/CanvasNode.tsx +41 -29
  65. package/src/client/canvas/CanvasViewport.tsx +101 -66
  66. package/src/client/canvas/CommandPalette.tsx +61 -27
  67. package/src/client/canvas/ContextMenu.tsx +13 -20
  68. package/src/client/canvas/ContextPinBar.tsx +1 -5
  69. package/src/client/canvas/ContextPinHud.tsx +1 -6
  70. package/src/client/canvas/DockedNode.tsx +4 -4
  71. package/src/client/canvas/EdgeLayer.tsx +37 -36
  72. package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
  73. package/src/client/canvas/FocusFieldLayer.tsx +20 -22
  74. package/src/client/canvas/IntentLayer.tsx +31 -14
  75. package/src/client/canvas/Minimap.tsx +11 -16
  76. package/src/client/canvas/SelectionBar.tsx +4 -11
  77. package/src/client/canvas/ShortcutOverlay.tsx +3 -1
  78. package/src/client/canvas/SnapshotPanel.tsx +77 -95
  79. package/src/client/canvas/auto-fit.ts +15 -14
  80. package/src/client/canvas/snap-guides.ts +12 -12
  81. package/src/client/canvas/use-node-resize.ts +1 -5
  82. package/src/client/canvas/use-pan-zoom.ts +25 -26
  83. package/src/client/ext-app/bridge.ts +3 -12
  84. package/src/client/icons.tsx +63 -20
  85. package/src/client/nodes/ContextNode.tsx +14 -25
  86. package/src/client/nodes/ExtAppFrame.tsx +60 -39
  87. package/src/client/nodes/FileNode.tsx +74 -62
  88. package/src/client/nodes/GroupNode.tsx +4 -6
  89. package/src/client/nodes/HtmlNode.tsx +76 -46
  90. package/src/client/nodes/ImageNode.tsx +18 -27
  91. package/src/client/nodes/InlineFormatBar.tsx +4 -21
  92. package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
  93. package/src/client/nodes/LedgerNode.tsx +10 -4
  94. package/src/client/nodes/MarkdownNode.tsx +3 -10
  95. package/src/client/nodes/McpAppNode.tsx +26 -22
  96. package/src/client/nodes/MdFormatBar.tsx +10 -7
  97. package/src/client/nodes/PromptNode.tsx +23 -51
  98. package/src/client/nodes/ResponseNode.tsx +3 -13
  99. package/src/client/nodes/StatusNode.tsx +5 -9
  100. package/src/client/nodes/StatusSummary.tsx +2 -8
  101. package/src/client/nodes/WebpageNode.tsx +20 -14
  102. package/src/client/nodes/iframe-document-url.ts +25 -16
  103. package/src/client/nodes/image-warnings.ts +1 -7
  104. package/src/client/nodes/md-format.ts +20 -5
  105. package/src/client/state/attention-bridge.ts +4 -9
  106. package/src/client/state/attention-store.ts +1 -7
  107. package/src/client/state/canvas-store.ts +52 -36
  108. package/src/client/state/intent-bridge.ts +176 -112
  109. package/src/client/state/intent-store.ts +4 -1
  110. package/src/client/state/sse-bridge.ts +53 -70
  111. package/src/json-render/catalog.ts +12 -16
  112. package/src/json-render/charts/components.tsx +16 -20
  113. package/src/json-render/charts/extra-components.tsx +8 -16
  114. package/src/json-render/charts/extra-definitions.ts +1 -2
  115. package/src/json-render/charts/tufte-components.tsx +37 -20
  116. package/src/json-render/charts/tufte-definitions.ts +8 -2
  117. package/src/json-render/renderer/index.tsx +42 -22
  118. package/src/json-render/schema.ts +6 -3
  119. package/src/json-render/server.ts +33 -39
  120. package/src/mcp/canvas-access.ts +40 -121
  121. package/src/mcp/server.ts +206 -271
  122. package/src/server/agent-context.ts +63 -36
  123. package/src/server/ax-context.ts +7 -5
  124. package/src/server/ax-interaction.ts +176 -43
  125. package/src/server/ax-state-manager.ts +185 -41
  126. package/src/server/ax-state.ts +142 -47
  127. package/src/server/bundled-skills.ts +3 -3
  128. package/src/server/canvas-db.ts +213 -95
  129. package/src/server/canvas-operations.ts +177 -120
  130. package/src/server/canvas-provenance.ts +1 -4
  131. package/src/server/canvas-schema.ts +500 -102
  132. package/src/server/canvas-serialization.ts +27 -35
  133. package/src/server/canvas-state.ts +155 -58
  134. package/src/server/chart-template.ts +4 -5
  135. package/src/server/code-graph.ts +20 -7
  136. package/src/server/diagram-presets.ts +28 -29
  137. package/src/server/ext-app-lookup.ts +4 -20
  138. package/src/server/html-node-summary.ts +19 -10
  139. package/src/server/html-primitives.ts +326 -97
  140. package/src/server/html-surface.ts +6 -9
  141. package/src/server/image-source.ts +6 -4
  142. package/src/server/index.ts +321 -218
  143. package/src/server/intent-registry.ts +3 -6
  144. package/src/server/mcp-app-candidate.ts +5 -10
  145. package/src/server/mcp-app-host.ts +14 -38
  146. package/src/server/mcp-app-runtime.ts +12 -20
  147. package/src/server/mutation-history.ts +15 -5
  148. package/src/server/operations/composites.ts +38 -40
  149. package/src/server/operations/http.ts +5 -5
  150. package/src/server/operations/index.ts +7 -1
  151. package/src/server/operations/invoker.ts +4 -3
  152. package/src/server/operations/mcp.ts +29 -36
  153. package/src/server/operations/ops/annotation.ts +122 -10
  154. package/src/server/operations/ops/app.ts +102 -77
  155. package/src/server/operations/ops/ax-await.ts +17 -10
  156. package/src/server/operations/ops/ax-read.ts +347 -0
  157. package/src/server/operations/ops/ax-shared.ts +2 -7
  158. package/src/server/operations/ops/ax-state.ts +32 -14
  159. package/src/server/operations/ops/ax-timeline.ts +32 -19
  160. package/src/server/operations/ops/ax-work.ts +54 -37
  161. package/src/server/operations/ops/batch.ts +39 -14
  162. package/src/server/operations/ops/canvas-wire.ts +91 -0
  163. package/src/server/operations/ops/edges.ts +37 -25
  164. package/src/server/operations/ops/ext-app.ts +346 -0
  165. package/src/server/operations/ops/groups.ts +49 -20
  166. package/src/server/operations/ops/intent.ts +18 -12
  167. package/src/server/operations/ops/json-render.ts +239 -98
  168. package/src/server/operations/ops/nodes.ts +300 -111
  169. package/src/server/operations/ops/query.ts +62 -33
  170. package/src/server/operations/ops/snapshots.ts +35 -26
  171. package/src/server/operations/ops/validate.ts +2 -1
  172. package/src/server/operations/ops/viewport.ts +60 -16
  173. package/src/server/operations/ops/webview.ts +44 -18
  174. package/src/server/operations/registry.ts +2 -3
  175. package/src/server/operations/types.ts +7 -5
  176. package/src/server/operations/webview-runner.ts +1 -3
  177. package/src/server/placement.ts +8 -18
  178. package/src/server/server.ts +121 -1012
  179. package/src/server/spatial-analysis.ts +39 -25
  180. package/src/server/trace-manager.ts +3 -8
  181. package/src/server/web-artifacts.ts +23 -27
  182. package/src/server/webpage-node.ts +5 -13
  183. package/src/shared/auto-arrange.ts +12 -5
  184. package/src/shared/ax-intent.ts +1 -1
  185. package/src/shared/content-height-reporter.ts +8 -6
  186. package/src/shared/ext-app-tool-result.ts +2 -6
  187. package/src/shared/placement.ts +1 -4
  188. package/src/shared/semantic-attention.ts +39 -37
  189. package/src/shared/surface.ts +8 -4
package/src/mcp/server.ts CHANGED
@@ -20,14 +20,15 @@
20
20
  * ```
21
21
  */
22
22
 
23
+ import { readFileSync } from 'node:fs';
24
+ import { dirname, join } from 'node:path';
25
+ import { fileURLToPath } from 'node:url';
23
26
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
24
27
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
25
28
  import { z } from 'zod';
26
29
  import { canvasState, describeCanvasSchema } from '../server/index.js';
27
30
  import { AX_INTERACTION_TYPES } from '../server/ax-interaction.js';
28
31
  import { buildPendingAxActivity } from '../server/ax-state.js';
29
- import { isHtmlPrimitiveKind } from '../server/html-primitives.js';
30
- import type { HtmlPrimitiveKind } from '../server/html-primitives.js';
31
32
  import { registerOperationTools, registerCompositeTools } from '../server/operations/index.js';
32
33
  import { createCanvasAccess, refreshCanvasAccess, type CanvasAccess } from './canvas-access.js';
33
34
  import { serializeNodeForAgentContext } from '../server/agent-context.js';
@@ -36,7 +37,6 @@ import {
36
37
  getCanvasNodeTitle,
37
38
  serializeCanvasLayoutForAgent,
38
39
  serializeCanvasNode,
39
- serializeCanvasNodeForAgent,
40
40
  summarizeCanvasAnnotationForContext,
41
41
  } from '../server/canvas-serialization.js';
42
42
  import { listBundledSkills, readBundledSkill } from '../server/bundled-skills.js';
@@ -46,7 +46,18 @@ let resourceNotificationServer: McpServer | null = null;
46
46
  let localResourceNotificationsStarted = false;
47
47
  let remoteResourceNotificationsBaseUrl: string | null = null;
48
48
 
49
- const htmlPrimitiveKindSchema = z.string().refine(isHtmlPrimitiveKind, 'Unknown HTML primitive kind');
49
+ // Resolved from the sibling package.json so it stays accurate through bunx,
50
+ // global npm installs, and repo-local runs (no hard-coded string, no build
51
+ // step required) — same mechanism as --version in src/cli/index.ts.
52
+ function readPackageVersion(): string {
53
+ try {
54
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
55
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: string };
56
+ return pkg.version ?? 'unknown';
57
+ } catch {
58
+ return 'unknown';
59
+ }
60
+ }
50
61
 
51
62
  function structuredSchemaDescription(): string {
52
63
  const routing = describeCanvasSchema().mcp.nodeTypeRouting;
@@ -55,10 +66,6 @@ function structuredSchemaDescription(): string {
55
66
  .join(', ');
56
67
  }
57
68
 
58
- // workspaceRoot / isPathInside / safeWorkspacePath removed with the
59
- // canvas_build_web_artifact MCP tool (plan-008 Wave 4). The webartifact.build op
60
- // sandboxes projectPath/outputPath via web-artifacts.ts resolveWorkspacePath.
61
-
62
69
  async function ensureCanvas(): Promise<CanvasAccess> {
63
70
  if (!canvas) {
64
71
  canvas = await createCanvasAccess();
@@ -190,12 +197,19 @@ function compactNodePayload(node: Awaited<ReturnType<CanvasAccess['getNode']>>):
190
197
  };
191
198
  }
192
199
 
193
- function compactLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>, pinnedIds: string[]): Record<string, unknown> {
200
+ function compactLayoutPayload(
201
+ layout: Awaited<ReturnType<CanvasAccess['getLayout']>>,
202
+ pinnedIds: string[],
203
+ ): Record<string, unknown> {
194
204
  return {
195
205
  summary: buildSummaryFromLayout(layout, pinnedIds),
196
206
  viewport: layout.viewport,
197
- annotations: (layout.annotations ?? []).map((annotation) => summarizeCanvasAnnotationForContext(annotation, layout.nodes)),
198
- nodes: layout.nodes.map((node) => compactNodePayload(node)).filter((node): node is Record<string, unknown> => node !== null),
207
+ annotations: (layout.annotations ?? []).map((annotation) =>
208
+ summarizeCanvasAnnotationForContext(annotation, layout.nodes),
209
+ ),
210
+ nodes: layout.nodes
211
+ .map((node) => compactNodePayload(node))
212
+ .filter((node): node is Record<string, unknown> => node !== null),
199
213
  edges: layout.edges.map((edge) => ({
200
214
  id: edge.id,
201
215
  from: edge.from,
@@ -211,24 +225,16 @@ function compactLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout
211
225
  function agentSafeFullLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>): Record<string, unknown> {
212
226
  return {
213
227
  ...serializeCanvasLayoutForAgent(layout),
214
- annotations: (layout.annotations ?? []).map((annotation) => summarizeCanvasAnnotationForContext(annotation, layout.nodes)),
228
+ annotations: (layout.annotations ?? []).map((annotation) =>
229
+ summarizeCanvasAnnotationForContext(annotation, layout.nodes),
230
+ ),
215
231
  };
216
232
  }
217
233
 
218
- async function createdNodePayload(c: CanvasAccess, id: string, options: { full?: boolean; verbose?: boolean; includeData?: boolean } = {}): Promise<Record<string, unknown>> {
219
- // Expose both `id` and a `nodeId` alias on every node-create response so
220
- // agents using either key (or a cached schema) work — matching the
221
- // external-app / web-artifact responses that already return both.
222
- const node = await c.getNode(id);
223
- if (!node) return { ok: true, id, nodeId: id };
224
- if (!wantsFullPayload(options)) {
225
- return { ok: true, node: compactNodePayload(node), id, nodeId: id };
226
- }
227
- const serialized = serializeCanvasNodeForAgent(node);
228
- return { ok: true, node: serialized, ...serialized, nodeId: node.id };
229
- }
230
-
231
- function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>, pinnedIds: string[]): Record<string, unknown> {
234
+ function buildSummaryFromLayout(
235
+ layout: Awaited<ReturnType<CanvasAccess['getLayout']>>,
236
+ pinnedIds: string[],
237
+ ): Record<string, unknown> {
232
238
  const pinned = new Set(pinnedIds);
233
239
  const nodesByType: Record<string, number> = {};
234
240
  const pinnedTitles: string[] = [];
@@ -241,7 +247,9 @@ function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayo
241
247
  totalNodes: layout.nodes.length,
242
248
  totalEdges: layout.edges.length,
243
249
  totalAnnotations: (layout.annotations ?? []).length,
244
- annotations: (layout.annotations ?? []).map((annotation) => summarizeCanvasAnnotationForContext(annotation, layout.nodes)),
250
+ annotations: (layout.annotations ?? []).map((annotation) =>
251
+ summarizeCanvasAnnotationForContext(annotation, layout.nodes),
252
+ ),
245
253
  nodesByType,
246
254
  pinnedCount: pinned.size,
247
255
  pinnedTitles,
@@ -252,15 +260,19 @@ function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayo
252
260
  export async function startMcpServer(): Promise<void> {
253
261
  const server = new McpServer({
254
262
  name: 'pmx-canvas',
255
- version: '0.1.0',
263
+ version: readPackageVersion(),
256
264
  });
257
265
  resourceNotificationServer = server;
258
266
 
259
267
  // ── Operation-registry tools (plan-005) ────────────────────────
260
- // canvas_get_layout / canvas_get_node / canvas_add_node /
261
- // canvas_update_node / canvas_remove_node are registered from the shared
262
- // operation registry. Tool names and compact/full payload behavior are
263
- // frozen (tests/unit/mcp-tool-freeze.test.ts, operation-parity.test.ts).
268
+ // Standalone tools are registered from the shared operation registry for
269
+ // every op not folded into a composite (registerOperationTools skips
270
+ // composite-folded ops see compositeFoldedOpNames in composites.ts).
271
+ // canvas_get_layout / canvas_get_node / canvas_add_node / canvas_update_node /
272
+ // canvas_remove_node were folded into canvas_query / canvas_node and removed
273
+ // as standalone tools in v0.3.0; they remain reachable via their composite
274
+ // and canvas_batch. Remaining standalone tool names and compact/full payload
275
+ // behavior are frozen (tests/unit/mcp-tool-freeze.test.ts, operation-parity.test.ts).
264
276
  registerOperationTools(server, ensureCanvas);
265
277
 
266
278
  // ── Composite (action-discriminated) tools (plan-006) ───────────
@@ -268,177 +280,51 @@ export async function startMcpServer(): Promise<void> {
268
280
  // (canvas_node, canvas_render, canvas_edge, canvas_group, canvas_history,
269
281
  // canvas_view, canvas_query, plus the AX composites). Each action dispatches
270
282
  // to the same registered operation as its standalone tool, so behavior is
271
- // identical. Additive in v0.2 (legacy tools still registered below); legacy
272
- // removed in v0.3 per docs/api-stability.md. (canvas_snapshot composite is
273
- // deferred to v0.3 — its name is still held by the legacy save-snapshot tool.)
283
+ // identical. The legacy single-purpose tools these composites replace are
284
+ // removed as of v0.3.0 per docs/api-stability.md. (canvas_snapshot composite
285
+ // is deferred to v0.4 — its name is still held by the legacy save-snapshot
286
+ // tool; the 6 snapshot standalones are deprecated in v0.3.0 and fold into it.)
274
287
  registerCompositeTools(server, ensureCanvas);
275
288
 
276
- // ── canvas_add_html_node ────────────────────────────────────────
277
- server.tool(
278
- 'canvas_add_html_node',
279
- 'Deprecated: use canvas_node with action "add" and type:"html". Add a normal html node: a self-contained HTML document (with optional inline <script> and CDN <script src="...">) rendered inside a sandboxed iframe (sandbox="allow-scripts"). This is the default HTML surface for reports, widgets, and bespoke visualizations. Presentation mode is opt-in: only pass presentation:true when the user explicitly asks for a deck/fullscreen presentation, or use canvas_add_html_primitive with kind="presentation". The iframe inherits live canvas theme tokens via injected CSS custom properties (both --c-* and common --color-* aliases) so authored HTML using var(--color-text-secondary), var(--color-bg), etc. renders cohesively. No same-origin access; no top-navigation; no forms. For declarative-only views with zero JS, prefer canvas_add_json_render_node. For React + shadcn + routing or multi-component apps, use canvas_build_web_artifact.',
280
- {
281
- html: z.string().describe('HTML document or fragment. Full <html>...</html> documents are passed through with theme styles injected into <head>; bare fragments are wrapped in a minimal document. Inline <script> and remote CDN <script src="..."> are allowed. If this is a bare path to an existing local .html/.htm file, the file contents are read and used as the HTML.'),
282
- title: z.string().optional().describe('Node title shown in the canvas titlebar.'),
283
- summary: z.string().optional().describe('Agent-readable semantic summary for this HTML node. If omitted, PMX derives one from visible HTML text.'),
284
- agentSummary: z.string().optional().describe('Explicit agent-readable summary. Alias for summary with higher priority when both are provided.'),
285
- description: z.string().optional().describe('Short description included in search and pinned/spatial context.'),
286
- presentation: z.boolean().optional().describe('Marks this HTML surface as a fullscreen presentation/deck. Omit unless the user explicitly requested presentation mode.'),
287
- slideTitles: z.array(z.string()).optional().describe('Agent-readable slide titles for presentation HTML.'),
288
- embeddedNodeIds: z.array(z.string()).optional().describe('Canvas node IDs embedded or represented by this HTML surface.'),
289
- embeddedUrls: z.array(z.string()).optional().describe('URLs embedded or represented by this HTML surface.'),
290
- x: z.number().optional().describe('X position (auto-placed if omitted).'),
291
- y: z.number().optional().describe('Y position (auto-placed if omitted).'),
292
- width: z.number().optional().describe('Width in pixels (default: 720).'),
293
- height: z.number().optional().describe('Height in pixels (default: 640).'),
294
- strictSize: z.boolean().optional().describe('Keep explicit width/height fixed; iframe scrolls overflow internally.'),
295
- axCapabilities: z.object({
296
- enabled: z.boolean().optional(),
297
- allowed: z.array(z.string()).optional().describe('AX interaction types this node may emit (e.g. ax.work.create, ax.work.update, ax.steer, ax.focus.set, ax.evidence.add, ax.event.record). Clamped to the html capability ceiling server-side; cannot escalate.'),
298
- }).optional().describe('Opt this html node into AX interactions so its sandboxed UI can emit ax.* via window.PMX_AX.emit(type, payload) (and reflect live AX state). html nodes are AX-disabled by default; set { enabled: true, allowed: [...] } to turn the bridge on. Build interactive boards (work queues, review boards, inboxes) this way.'),
299
- full: z.boolean().optional().describe('Return the full created node payload. Default false returns compact metadata.'),
300
- verbose: z.boolean().optional().describe('Alias for full:true.'),
301
- },
302
- async (input) => {
303
- const c = await ensureCanvas();
304
- const id = await c.addHtmlNode({
305
- html: input.html,
306
- ...(typeof input.title === 'string' ? { title: input.title } : {}),
307
- ...(input.axCapabilities ? { axCapabilities: input.axCapabilities } : {}),
308
- ...(typeof input.summary === 'string' ? { summary: input.summary } : {}),
309
- ...(typeof input.agentSummary === 'string' ? { agentSummary: input.agentSummary } : {}),
310
- ...(typeof input.description === 'string' ? { description: input.description } : {}),
311
- ...(input.presentation === true ? { presentation: true } : {}),
312
- ...(Array.isArray(input.slideTitles) ? { slideTitles: input.slideTitles } : {}),
313
- ...(Array.isArray(input.embeddedNodeIds) ? { embeddedNodeIds: input.embeddedNodeIds } : {}),
314
- ...(Array.isArray(input.embeddedUrls) ? { embeddedUrls: input.embeddedUrls } : {}),
315
- ...(typeof input.x === 'number' ? { x: input.x } : {}),
316
- ...(typeof input.y === 'number' ? { y: input.y } : {}),
317
- ...(typeof input.width === 'number' ? { width: input.width } : {}),
318
- ...(typeof input.height === 'number' ? { height: input.height } : {}),
319
- ...(input.strictSize === true ? { strictSize: true } : {}),
320
- });
321
- return {
322
- content: [{ type: 'text', text: JSON.stringify(await createdNodePayload(c, id, input), null, 2) }],
323
- };
324
- },
325
- );
326
-
327
- server.tool(
328
- 'canvas_add_html_primitive',
329
- 'Deprecated: use canvas_node with action "add", type:"html", primitive:"<kind>" (and data). Create a reusable HTML communication primitive as a normal sandboxed html node. Use this instead of long markdown for side-by-side choices, implementation plans, PR review sheets, module maps, design sheets, component galleries, flowcharts, explainers, status reports, and throwaway editors with export/copy paths. Use kind="presentation" only when the user explicitly asks for a PowerPoint-like deck, pitch, briefing, workshop walkthrough, or fullscreen story.',
330
- {
331
- kind: htmlPrimitiveKindSchema.describe('Primitive kind. Call canvas_describe_schema and read htmlPrimitives for data shapes and examples.'),
332
- title: z.string().optional().describe('Node title shown in the canvas titlebar.'),
333
- data: z.record(z.string(), z.unknown()).optional().describe('Primitive-specific data payload. For kind="presentation", data may include theme:"canvas"|"midnight"|"paper"|"aurora" or a custom color object. See canvas_describe_schema.htmlPrimitives for each shape.'),
334
- x: z.number().optional().describe('X position (auto-placed if omitted).'),
335
- y: z.number().optional().describe('Y position (auto-placed if omitted).'),
336
- width: z.number().optional().describe('Width in pixels (defaults per primitive).'),
337
- height: z.number().optional().describe('Height in pixels (defaults per primitive).'),
338
- strictSize: z.boolean().optional().describe('Keep explicit width/height fixed; iframe scrolls overflow internally.'),
339
- full: z.boolean().optional().describe('Return the full created node payload. Default false returns compact metadata.'),
340
- verbose: z.boolean().optional().describe('Alias for full:true.'),
341
- },
342
- async (input) => {
343
- const c = await ensureCanvas();
344
- const kind = input.kind as HtmlPrimitiveKind;
345
- const result = await c.addHtmlPrimitive({
346
- kind,
347
- ...(typeof input.title === 'string' ? { title: input.title } : {}),
348
- ...(input.data ? { data: input.data } : {}),
349
- ...(typeof input.x === 'number' ? { x: input.x } : {}),
350
- ...(typeof input.y === 'number' ? { y: input.y } : {}),
351
- ...(typeof input.width === 'number' ? { width: input.width } : {}),
352
- ...(typeof input.height === 'number' ? { height: input.height } : {}),
353
- ...(input.strictSize === true ? { strictSize: true } : {}),
354
- });
355
- return {
356
- content: [{
357
- type: 'text',
358
- text: JSON.stringify({
359
- ...(await createdNodePayload(c, result.id, input)),
360
- primitive: { kind: result.kind, title: result.title, htmlBytes: result.htmlBytes },
361
- }, null, 2),
362
- }],
363
- };
364
- },
365
- );
366
-
367
- // canvas_open_mcp_app + canvas_add_diagram migrated to the operation registry
368
- // (plan-008 Wave 4): src/server/operations/ops/app.ts (mcpapp.open /
369
- // diagram.open). Folded into the canvas_app composite.
370
-
371
- server.tool(
372
- 'canvas_refresh_webpage_node',
373
- 'Deprecated: use canvas_node with action "update" and refresh:true. Refresh a webpage node from its persisted URL so the server re-fetches and caches the latest page text and metadata.',
374
- {
375
- id: z.string().describe('Webpage node ID to refresh'),
376
- url: z.string().optional().describe('Optional replacement URL before refresh'),
377
- },
378
- async ({ id, url }) => {
379
- const c = await ensureCanvas();
380
- const result = await c.refreshWebpageNode(id, url);
381
- return {
382
- content: [{ type: 'text', text: JSON.stringify(result) }],
383
- ...(result.ok ? {} : { isError: true }),
384
- };
385
- },
386
- );
387
-
388
- // canvas_build_web_artifact migrated to the operation registry (plan-008
389
- // Wave 4): src/server/operations/ops/app.ts (webartifact.build). Folded into
390
- // the canvas_app composite.
391
-
392
- // canvas_remove_annotation migrated to the operation registry (plan-008
393
- // Wave 1): src/server/operations/ops/annotation.ts.
394
-
395
- // ── AX context and focus ───────────────────────────────────────
396
- // canvas_get_ax + canvas_set_ax_focus migrated to the operation registry
397
- // (plan-007 Slice B.1): src/server/operations/ops/ax-state.ts.
398
-
399
- // canvas_record_ax_event / canvas_send_steering / canvas_get_ax_timeline
400
- // migrated to the operation registry (plan-007 Slice B wave 3):
401
- // src/server/operations/ops/ax-timeline.ts.
402
-
403
- // canvas_add_work_item / canvas_update_work_item / canvas_request_approval /
404
- // canvas_resolve_approval migrated to the operation registry (plan-007 Slice B
405
- // wave 2): src/server/operations/ops/ax-work.ts.
406
-
407
- // canvas_add_evidence migrated to the operation registry (plan-007 Slice B
408
- // wave 3): src/server/operations/ops/ax-timeline.ts.
409
-
410
- // canvas_add_review_annotation migrated to the operation registry (plan-007
411
- // Slice B wave 2): src/server/operations/ops/ax-work.ts.
412
-
413
- // canvas_report_host_capability migrated to the operation registry
414
- // (plan-007 Slice B.1): src/server/operations/ops/ax-state.ts.
415
-
416
289
  server.tool(
417
290
  'canvas_ax_interaction',
418
291
  'Submit a node-originated AX interaction: a capability-gated, validated event from an eligible node that maps onto an AX operation (work item, evidence, approval, review, focus, steering, event). Returns { ok: false, code } if the node type/metadata does not allow the interaction type or the payload is invalid.',
419
292
  {
420
- type: z.enum(AX_INTERACTION_TYPES).describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
293
+ type: z
294
+ .enum(AX_INTERACTION_TYPES)
295
+ .describe('Interaction type, e.g. ax.work.create, ax.evidence.add, ax.focus.set.'),
421
296
  sourceNodeId: z.string().describe('The node emitting the interaction.'),
422
- payload: z.record(z.string(), z.unknown()).optional().describe('Type-specific payload, e.g. {"title":"..."} for ax.work.create.'),
297
+ payload: z
298
+ .record(z.string(), z.unknown())
299
+ .optional()
300
+ .describe('Type-specific payload, e.g. {"title":"..."} for ax.work.create.'),
423
301
  sourceSurface: z.enum(['native-node', 'json-render', 'html-node', 'mcp-app', 'adapter']).optional(),
424
302
  correlationId: z.string().optional(),
425
- source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
303
+ source: z
304
+ .enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system'])
426
305
  .optional()
427
306
  .describe('Optional host/source label. Defaults to mcp.'),
428
307
  },
429
308
  async ({ type, sourceNodeId, payload, sourceSurface, correlationId, source }) => {
430
- const c = await ensureCanvas();
431
- const result = await c.submitAxInteraction(
432
- {
433
- type,
434
- sourceNodeId,
435
- ...(payload ? { payload } : {}),
436
- ...(sourceSurface ? { sourceSurface } : {}),
437
- ...(correlationId ? { correlationId } : {}),
438
- },
439
- { source: source ?? 'mcp' },
440
- );
441
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
309
+ try {
310
+ const c = await ensureCanvas();
311
+ const result = await c.submitAxInteraction(
312
+ {
313
+ type,
314
+ sourceNodeId,
315
+ ...(payload ? { payload } : {}),
316
+ ...(sourceSurface ? { sourceSurface } : {}),
317
+ ...(correlationId ? { correlationId } : {}),
318
+ },
319
+ { source: source ?? 'mcp' },
320
+ );
321
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
322
+ } catch (error) {
323
+ return {
324
+ content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
325
+ isError: true,
326
+ };
327
+ }
442
328
  },
443
329
  );
444
330
 
@@ -453,47 +339,84 @@ export async function startMcpServer(): Promise<void> {
453
339
  'canvas_ingest_activity',
454
340
  'Ingest a normalized agent activity (a tool/session event your harness forwards) so the board reacts automatically — primitive A, makes AX bidirectional. Always records a timeline event; kind-driven default reactions (overridable per call via `reactions`): failure/error → work item (blocked) + review finding + evidence (logs); tool-result + outcome:"success" → evidence (tool-result); everything else (tool-start, session-*, command, note) → event only. Set any reaction to false to suppress it, or to an object to override its fields. Returns { event, workItem, evidence, review }.',
455
341
  {
456
- kind: z.enum(['tool-start', 'tool-result', 'failure', 'error', 'session-start', 'session-end', 'command', 'note']),
342
+ kind: z.enum([
343
+ 'tool-start',
344
+ 'tool-result',
345
+ 'failure',
346
+ 'error',
347
+ 'session-start',
348
+ 'session-end',
349
+ 'command',
350
+ 'note',
351
+ ]),
457
352
  title: z.string(),
458
353
  summary: z.string().optional(),
459
354
  outcome: z.enum(['success', 'failure']).optional(),
460
- ref: z.string().optional().describe('A file path, URL, or commit the activity refers to (used as the review file anchor for failures).'),
355
+ ref: z
356
+ .string()
357
+ .optional()
358
+ .describe('A file path, URL, or commit the activity refers to (used as the review file anchor for failures).'),
461
359
  nodeIds: z.array(z.string()).optional(),
462
360
  data: z.record(z.string(), z.unknown()).optional(),
463
- reactions: z.object({
464
- workItem: z.union([z.literal(false), z.object({
465
- status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'cancelled']).optional(),
466
- detail: z.string().nullable().optional(),
467
- })]).optional(),
468
- evidence: z.union([z.literal(false), z.object({
469
- kind: z.enum(['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output']).optional(),
470
- body: z.string().nullable().optional(),
471
- })]).optional(),
472
- review: z.union([z.literal(false), z.object({
473
- severity: z.enum(['info', 'warning', 'error']).optional(),
474
- kind: z.enum(['comment', 'finding']).optional(),
475
- anchorType: z.enum(['node', 'file', 'region']).optional(),
476
- nodeId: z.string().nullable().optional(),
477
- })]).optional(),
478
- }).optional().describe('Override or suppress the kind-driven default reactions.'),
361
+ reactions: z
362
+ .object({
363
+ workItem: z
364
+ .union([
365
+ z.literal(false),
366
+ z.object({
367
+ status: z.enum(['todo', 'in-progress', 'blocked', 'done', 'cancelled']).optional(),
368
+ detail: z.string().nullable().optional(),
369
+ }),
370
+ ])
371
+ .optional(),
372
+ evidence: z
373
+ .union([
374
+ z.literal(false),
375
+ z.object({
376
+ kind: z.enum(['logs', 'tool-result', 'screenshot', 'file', 'diff', 'test-output']).optional(),
377
+ body: z.string().nullable().optional(),
378
+ }),
379
+ ])
380
+ .optional(),
381
+ review: z
382
+ .union([
383
+ z.literal(false),
384
+ z.object({
385
+ severity: z.enum(['info', 'warning', 'error']).optional(),
386
+ kind: z.enum(['comment', 'finding']).optional(),
387
+ anchorType: z.enum(['node', 'file', 'region']).optional(),
388
+ nodeId: z.string().nullable().optional(),
389
+ }),
390
+ ])
391
+ .optional(),
392
+ })
393
+ .optional()
394
+ .describe('Override or suppress the kind-driven default reactions.'),
479
395
  source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
480
396
  },
481
397
  async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source }) => {
482
- const c = await ensureCanvas();
483
- const result = await c.ingestActivity(
484
- {
485
- kind,
486
- title,
487
- ...(summary !== undefined ? { summary } : {}),
488
- ...(outcome !== undefined ? { outcome } : {}),
489
- ...(ref !== undefined ? { ref } : {}),
490
- ...(nodeIds !== undefined ? { nodeIds } : {}),
491
- ...(data !== undefined ? { data } : {}),
492
- ...(reactions !== undefined ? { reactions } : {}),
493
- },
494
- { source: source ?? 'mcp' },
495
- );
496
- return { content: [{ type: 'text', text: JSON.stringify({ ok: true, ...result }) }] };
398
+ try {
399
+ const c = await ensureCanvas();
400
+ const result = await c.ingestActivity(
401
+ {
402
+ kind,
403
+ title,
404
+ ...(summary !== undefined ? { summary } : {}),
405
+ ...(outcome !== undefined ? { outcome } : {}),
406
+ ...(ref !== undefined ? { ref } : {}),
407
+ ...(nodeIds !== undefined ? { nodeIds } : {}),
408
+ ...(data !== undefined ? { data } : {}),
409
+ ...(reactions !== undefined ? { reactions } : {}),
410
+ },
411
+ { source: source ?? 'mcp' },
412
+ );
413
+ return { content: [{ type: 'text', text: JSON.stringify({ ok: true, ...result }) }] };
414
+ } catch (error) {
415
+ return {
416
+ content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
417
+ isError: true,
418
+ };
419
+ }
497
420
  },
498
421
  );
499
422
 
@@ -516,9 +439,12 @@ export async function startMcpServer(): Promise<void> {
516
439
  // ── canvas_screenshot ─────────────────────────────────────────
517
440
  server.tool(
518
441
  'canvas_screenshot',
519
- 'Capture a screenshot from the active Bun.WebView automation session. Returns both an MCP image payload and JSON metadata. Requires an active automation session started via canvas_webview_start.',
442
+ 'Capture a screenshot from the active Bun.WebView automation session. Returns both an MCP image payload and JSON metadata. Requires an active automation session started via canvas_webview with action "start".',
520
443
  {
521
- format: z.enum(['png', 'jpeg', 'webp']).optional().describe('Screenshot format (default depends on Bun; png recommended)'),
444
+ format: z
445
+ .enum(['png', 'jpeg', 'webp'])
446
+ .optional()
447
+ .describe('Screenshot format (default depends on Bun; png recommended)'),
522
448
  quality: z.number().optional().describe('Optional quality for lossy formats'),
523
449
  },
524
450
  async ({ format, quality }) => {
@@ -534,27 +460,21 @@ export async function startMcpServer(): Promise<void> {
534
460
  {
535
461
  type: 'image',
536
462
  data: encodeBase64(bytes),
537
- mimeType:
538
- format === 'jpeg'
539
- ? 'image/jpeg'
540
- : format === 'webp'
541
- ? 'image/webp'
542
- : 'image/png',
463
+ mimeType: format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png',
543
464
  },
544
465
  {
545
466
  type: 'text',
546
- text: JSON.stringify({
547
- bytes: bytes.byteLength,
548
- backend: status.backend,
549
- width: status.width,
550
- height: status.height,
551
- mimeType:
552
- format === 'jpeg'
553
- ? 'image/jpeg'
554
- : format === 'webp'
555
- ? 'image/webp'
556
- : 'image/png',
557
- }, null, 2),
467
+ text: JSON.stringify(
468
+ {
469
+ bytes: bytes.byteLength,
470
+ backend: status.backend,
471
+ width: status.width,
472
+ height: status.height,
473
+ mimeType: format === 'jpeg' ? 'image/jpeg' : format === 'webp' ? 'image/webp' : 'image/png',
474
+ },
475
+ null,
476
+ 2,
477
+ ),
558
478
  },
559
479
  ],
560
480
  };
@@ -576,8 +496,7 @@ export async function startMcpServer(): Promise<void> {
576
496
  'schema',
577
497
  'canvas://schema',
578
498
  {
579
- description:
580
- `Machine-readable create schemas, canonical examples, json-render catalog details, and MCP node-type routing from the running PMX Canvas server version. Routing: ${structuredSchemaDescription()}.`,
499
+ description: `Machine-readable create schemas, canonical examples, json-render catalog details, and MCP node-type routing from the running PMX Canvas server version. Routing: ${structuredSchemaDescription()}.`,
581
500
  mimeType: 'application/json',
582
501
  },
583
502
  async () => ({
@@ -607,20 +526,20 @@ export async function startMcpServer(): Promise<void> {
607
526
  const layout = await c.getLayout();
608
527
 
609
528
  const pinnedNodes = layout.nodes.filter((n) => pinnedIds.has(n.id));
610
- const pinnedEdges = layout.edges.filter(
611
- (e) => pinnedIds.has(e.from) && pinnedIds.has(e.to),
612
- );
529
+ const pinnedEdges = layout.edges.filter((e) => pinnedIds.has(e.from) && pinnedIds.has(e.to));
613
530
 
614
531
  // Compute neighborhoods: for each pinned node, find nearby unpinned nodes
615
532
  const neighborhoods = findNeighborhoods(layout.nodes, pinnedIds);
616
533
 
617
534
  const context = {
618
535
  pinnedCount: pinnedNodes.length,
619
- nodes: pinnedNodes.map((n) => serializeNodeForAgentContext(n, {
620
- defaultTextLength: 700,
621
- webpageTextLength: 1600,
622
- includePosition: true,
623
- })),
536
+ nodes: pinnedNodes.map((n) =>
537
+ serializeNodeForAgentContext(n, {
538
+ defaultTextLength: 700,
539
+ webpageTextLength: 1600,
540
+ includePosition: true,
541
+ }),
542
+ ),
624
543
  edges: pinnedEdges.map((e) => ({
625
544
  id: e.id,
626
545
  from: e.from,
@@ -674,8 +593,7 @@ export async function startMcpServer(): Promise<void> {
674
593
  'ax-context',
675
594
  'canvas://ax-context',
676
595
  {
677
- description:
678
- 'Agent-ready PMX AX context combining pinned context, focus, and surface metadata.',
596
+ description: 'Agent-ready PMX AX context combining pinned context, focus, and surface metadata.',
679
597
  mimeType: 'application/json',
680
598
  },
681
599
  async () => {
@@ -733,14 +651,18 @@ export async function startMcpServer(): Promise<void> {
733
651
  {
734
652
  uri: 'canvas://ax-work',
735
653
  mimeType: 'application/json',
736
- text: JSON.stringify({
737
- workItems,
738
- approvalGates,
739
- reviewAnnotations: state.reviewAnnotations,
740
- elicitations: state.elicitations,
741
- modeRequests: state.modeRequests,
742
- policy: state.policy,
743
- }, null, 2),
654
+ text: JSON.stringify(
655
+ {
656
+ workItems,
657
+ approvalGates,
658
+ reviewAnnotations: state.reviewAnnotations,
659
+ elicitations: state.elicitations,
660
+ modeRequests: state.modeRequests,
661
+ policy: state.policy,
662
+ },
663
+ null,
664
+ 2,
665
+ ),
744
666
  },
745
667
  ],
746
668
  };
@@ -752,7 +674,7 @@ export async function startMcpServer(): Promise<void> {
752
674
  'canvas://ax-pending-steering',
753
675
  {
754
676
  description:
755
- 'Adapterless AX delivery surface. `pending`: undelivered steering messages to claim and act on, then mark via canvas_mark_ax_delivery. `pendingActivity`: open canvas-bound AX items awaiting the agent (open work items, pending approval gates / elicitations / mode requests) — usually created by the human in the browser; these fire ax-state-changed (resource-subscribers are also pushed canvas://ax-work). Resolve pendingActivity via its own tool, not canvas_mark_ax_delivery. Use canvas_claim_ax_delivery for the loop-safe, consumer-scoped view.',
677
+ 'Adapterless AX delivery surface. `pending`: undelivered steering messages to claim and act on, then mark via canvas_ax_delivery { action: "mark" }. `pendingActivity`: open canvas-bound AX items awaiting the agent (open work items, pending approval gates / elicitations / mode requests) — usually created by the human in the browser; these fire ax-state-changed (resource-subscribers are also pushed canvas://ax-work). Resolve pendingActivity via its own tool, not the delivery mark action. Use canvas_ax_delivery { action: "claim" } for the loop-safe, consumer-scoped view.',
756
678
  mimeType: 'application/json',
757
679
  },
758
680
  async () => {
@@ -761,7 +683,11 @@ export async function startMcpServer(): Promise<void> {
761
683
  const pendingActivity = buildPendingAxActivity(state);
762
684
  return {
763
685
  contents: [
764
- { uri: 'canvas://ax-pending-steering', mimeType: 'application/json', text: JSON.stringify({ pending, pendingActivity }, null, 2) },
686
+ {
687
+ uri: 'canvas://ax-pending-steering',
688
+ mimeType: 'application/json',
689
+ text: JSON.stringify({ pending, pendingActivity }, null, 2),
690
+ },
765
691
  ],
766
692
  };
767
693
  },
@@ -780,7 +706,11 @@ export async function startMcpServer(): Promise<void> {
780
706
  const timeline = await c.getAxTimeline();
781
707
  return {
782
708
  contents: [
783
- { uri: 'canvas://ax-delivery', mimeType: 'application/json', text: JSON.stringify({ steering: timeline.steering }, null, 2) },
709
+ {
710
+ uri: 'canvas://ax-delivery',
711
+ mimeType: 'application/json',
712
+ text: JSON.stringify({ steering: timeline.steering }, null, 2),
713
+ },
784
714
  ],
785
715
  };
786
716
  },
@@ -859,13 +789,18 @@ export async function startMcpServer(): Promise<void> {
859
789
  'grouped together), provides reading order (top-left to bottom-right), and shows ' +
860
790
  'neighborhoods around pinned nodes (nearby unpinned nodes the human implicitly associated). ' +
861
791
  'This makes "spatial arrangement is communication" real — read this to understand the ' +
862
- 'human\'s spatial intent, not just which nodes are pinned.',
792
+ "human's spatial intent, not just which nodes are pinned.",
863
793
  mimeType: 'application/json',
864
794
  },
865
795
  async () => {
866
796
  const c = await ensureCanvas();
867
797
  const layout = await c.getLayout();
868
- const spatial = buildSpatialContext(layout.nodes, layout.edges, new Set(await c.getPinnedNodeIds()), layout.annotations ?? []);
798
+ const spatial = buildSpatialContext(
799
+ layout.nodes,
800
+ layout.edges,
801
+ new Set(await c.getPinnedNodeIds()),
802
+ layout.annotations ?? [],
803
+ );
869
804
  return {
870
805
  contents: [
871
806
  {
@@ -931,8 +866,8 @@ export async function startMcpServer(): Promise<void> {
931
866
  // ── canvas://skills ────────────────────────────────────────
932
867
  // Discoverability for the skill prompts bundled with the npm package
933
868
  // (skills/<name>/SKILL.md). Before 0.1.2 these files shipped but were
934
- // invisible to agents — calling canvas_build_web_artifact without the
935
- // companion `web-artifacts-builder` skill led to predictable misuse.
869
+ // invisible to agents — building web artifacts without the companion
870
+ // `web-artifacts-builder` skill led to predictable misuse.
936
871
  // The index lists every bundled skill with its frontmatter description;
937
872
  // individual skills are served verbatim at canvas://skills/<name>.
938
873
  server.resource(
@@ -943,7 +878,7 @@ export async function startMcpServer(): Promise<void> {
943
878
  'Index of agent skills bundled with this PMX Canvas install. Lists name, ' +
944
879
  'description, and per-skill URI (canvas://skills/<name>). Read a specific ' +
945
880
  'skill for workflow guidance — notably web-artifacts-builder for ' +
946
- 'canvas_build_web_artifact, and pmx-canvas for the broader workbench.',
881
+ 'canvas_app { action: "build-artifact" }, and pmx-canvas for the broader workbench.',
947
882
  mimeType: 'application/json',
948
883
  },
949
884
  async () => {