pmx-canvas 0.1.5 → 0.1.7

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.
@@ -22,6 +22,7 @@ export interface CanvasCreateTypeSchema {
22
22
  kind: 'node' | 'virtual-node';
23
23
  description: string;
24
24
  endpoint: string;
25
+ mcpTool?: string;
25
26
  fields: CanvasCreateField[];
26
27
  example: Record<string, unknown>;
27
28
  notes?: string[];
@@ -63,6 +64,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
63
64
  kind: 'node',
64
65
  description: 'Freeform markdown note.',
65
66
  endpoint: '/api/canvas/node',
67
+ mcpTool: 'canvas_add_node',
66
68
  fields: [
67
69
  { name: 'title', type: 'string', required: false, description: 'Optional node title.' },
68
70
  { name: 'content', type: 'string', required: false, description: 'Markdown body.' },
@@ -85,6 +87,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
85
87
  kind: 'node',
86
88
  description: 'Compact status indicator.',
87
89
  endpoint: '/api/canvas/node',
90
+ mcpTool: 'canvas_add_node',
88
91
  fields: [
89
92
  { name: 'title', type: 'string', required: false, description: 'Status label.' },
90
93
  { name: 'content', type: 'string', required: false, description: 'Rendered status text.' },
@@ -100,6 +103,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
100
103
  kind: 'node',
101
104
  description: 'Agent context card container.',
102
105
  endpoint: '/api/canvas/node',
106
+ mcpTool: 'canvas_add_node',
103
107
  fields: [
104
108
  { name: 'title', type: 'string', required: false, description: 'Optional title override.' },
105
109
  { name: 'content', type: 'string', required: false, description: 'Optional context body.' },
@@ -115,6 +119,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
115
119
  kind: 'node',
116
120
  description: 'Structured ledger/log node.',
117
121
  endpoint: '/api/canvas/node',
122
+ mcpTool: 'canvas_add_node',
118
123
  fields: [
119
124
  { name: 'title', type: 'string', required: false, description: 'Optional title.' },
120
125
  { name: 'content', type: 'string', required: false, description: 'Ledger body text.' },
@@ -130,6 +135,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
130
135
  kind: 'node',
131
136
  description: 'Execution trace viewer.',
132
137
  endpoint: '/api/canvas/node',
138
+ mcpTool: 'canvas_add_node',
133
139
  fields: [
134
140
  { name: 'title', type: 'string', required: false, description: 'Optional title.' },
135
141
  { name: 'content', type: 'string', required: false, description: 'Trace summary.' },
@@ -145,6 +151,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
145
151
  kind: 'node',
146
152
  description: 'Workspace file viewer.',
147
153
  endpoint: '/api/canvas/node',
154
+ mcpTool: 'canvas_add_node',
148
155
  fields: [
149
156
  { name: 'content', type: 'string', required: true, description: 'Workspace-relative or absolute file path.' },
150
157
  { name: 'title', type: 'string', required: false, description: 'Optional title override.' },
@@ -162,6 +169,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
162
169
  kind: 'node',
163
170
  description: 'Image node backed by a path, URL, or data URI.',
164
171
  endpoint: '/api/canvas/node',
172
+ mcpTool: 'canvas_add_node',
165
173
  fields: [
166
174
  { name: 'content', type: 'string', required: true, description: 'Image path, URL, or data URI.' },
167
175
  { name: 'title', type: 'string', required: false, description: 'Optional title override.' },
@@ -187,6 +195,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
187
195
  kind: 'node',
188
196
  description: 'Persisted webpage snapshot with server-side fetch and refresh.',
189
197
  endpoint: '/api/canvas/node',
198
+ mcpTool: 'canvas_add_node',
190
199
  fields: [
191
200
  { name: 'url', type: 'string', required: true, description: 'HTTP(S) URL to fetch and cache.', aliases: ['content'] },
192
201
  { name: 'title', type: 'string', required: false, description: 'Optional title override.' },
@@ -210,6 +219,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
210
219
  kind: 'node',
211
220
  description: 'Hosted iframe/app node.',
212
221
  endpoint: '/api/canvas/node',
222
+ mcpTool: 'canvas_open_mcp_app',
213
223
  fields: [
214
224
  { name: 'title', type: 'string', required: false, description: 'App title.' },
215
225
  { name: 'content', type: 'string', required: false, description: 'Optional inline content.' },
@@ -223,11 +233,35 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
223
233
  'Tool-backed MCP app nodes and hosted artifact nodes persist `data.provenance` when the server can infer a reopen or rehydrate path.',
224
234
  ],
225
235
  },
236
+ {
237
+ type: 'external-app',
238
+ kind: 'virtual-node',
239
+ description: 'Tool-backed hosted app opened from an external MCP server, such as Excalidraw.',
240
+ endpoint: '/api/canvas/mcp-app/open',
241
+ mcpTool: 'canvas_open_mcp_app',
242
+ fields: [
243
+ { name: 'toolName', type: 'string', required: true, description: 'Tool name on the external MCP server.' },
244
+ { name: 'transport', type: '{ type: "stdio", command, args? } | { type: "http", url, headers? }', required: true, description: 'External MCP transport definition.' },
245
+ { name: 'toolArguments', type: 'record<string, unknown>', required: false, description: 'Arguments passed to the external MCP tool.' },
246
+ { name: 'title', type: 'string', required: false, description: 'Optional canvas node title override.' },
247
+ ],
248
+ example: {
249
+ toolName: 'create_view',
250
+ transport: { type: 'http', url: 'https://mcp.excalidraw.com/mcp' },
251
+ toolArguments: { elements: '[]' },
252
+ title: 'Excalidraw Diagram',
253
+ },
254
+ notes: [
255
+ 'For Excalidraw specifically, prefer canvas_add_diagram because it fills in the built-in transport, toolName, and checkpoint wiring.',
256
+ 'The CLI convenience command `external-app add --kind excalidraw` maps to this built-in Excalidraw preset; MCP canvas_open_mcp_app is the lower-level transport form.',
257
+ ],
258
+ },
226
259
  {
227
260
  type: 'group',
228
261
  kind: 'node',
229
262
  description: 'Canvas group frame.',
230
263
  endpoint: '/api/canvas/group',
264
+ mcpTool: 'canvas_create_group',
231
265
  fields: [
232
266
  { name: 'title', type: 'string', required: false, description: 'Group title.' },
233
267
  { name: 'childIds', type: 'string[]', required: false, description: 'Initial child node IDs.' },
@@ -244,6 +278,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
244
278
  kind: 'virtual-node',
245
279
  description: 'Native structured UI panel rendered from a validated json-render spec.',
246
280
  endpoint: '/api/canvas/json-render',
281
+ mcpTool: 'canvas_add_json_render_node',
247
282
  fields: [
248
283
  { name: 'title', type: 'string', required: true, description: 'Rendered node title.' },
249
284
  { name: 'spec', type: 'JsonRenderSpec', required: true, description: 'Complete json-render spec.' },
@@ -276,6 +311,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
276
311
  kind: 'virtual-node',
277
312
  description: 'Native chart node backed by the json-render chart catalog.',
278
313
  endpoint: '/api/canvas/graph',
314
+ mcpTool: 'canvas_add_graph_node',
279
315
  fields: [
280
316
  {
281
317
  name: 'graphType',
@@ -325,6 +361,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
325
361
  kind: 'virtual-node',
326
362
  description: 'Bundled single-file HTML artifact that can open as an embedded canvas node.',
327
363
  endpoint: '/api/canvas/web-artifact',
364
+ mcpTool: 'canvas_build_web_artifact',
328
365
  fields: [
329
366
  { name: 'title', type: 'string', required: true, description: 'Artifact title used for default paths.' },
330
367
  { name: 'appTsx', type: 'string', required: true, description: 'Contents for src/App.tsx. The CLI also accepts piped contents via --stdin.', aliases: ['app-file', 'app-tsx'] },
@@ -349,6 +386,16 @@ function clone<T>(value: T): T {
349
386
  return JSON.parse(JSON.stringify(value)) as T;
350
387
  }
351
388
 
389
+ function buildMcpNodeTypeRouting(nodeTypes: CanvasCreateTypeSchema[]): Record<string, string> {
390
+ const routing: Record<string, string> = {};
391
+ for (const entry of nodeTypes) {
392
+ if (typeof entry.mcpTool === 'string') {
393
+ routing[entry.type] = entry.mcpTool;
394
+ }
395
+ }
396
+ return routing;
397
+ }
398
+
352
399
  export function describeCanvasSchema(): {
353
400
  ok: true;
354
401
  source: 'running-server';
@@ -364,13 +411,15 @@ export function describeCanvasSchema(): {
364
411
  mcp: {
365
412
  tools: string[];
366
413
  resources: string[];
414
+ nodeTypeRouting: Record<string, string>;
367
415
  };
368
416
  } {
417
+ const nodeTypes = clone(CANVAS_CREATE_TYPES);
369
418
  return {
370
419
  ok: true,
371
420
  source: 'running-server',
372
421
  version: readPackageVersion(),
373
- nodeTypes: clone(CANVAS_CREATE_TYPES),
422
+ nodeTypes,
374
423
  jsonRender: {
375
424
  rootShape: {
376
425
  root: 'string',
@@ -388,10 +437,13 @@ export function describeCanvasSchema(): {
388
437
  'canvas_add_json_render_node',
389
438
  'canvas_add_graph_node',
390
439
  'canvas_build_web_artifact',
440
+ 'canvas_open_mcp_app',
441
+ 'canvas_create_group',
391
442
  'canvas_describe_schema',
392
443
  'canvas_validate_spec',
393
444
  ],
394
445
  resources: ['canvas://schema'],
446
+ nodeTypeRouting: buildMcpNodeTypeRouting(nodeTypes),
395
447
  },
396
448
  };
397
449
  }
@@ -473,6 +473,10 @@ export async function evaluateCanvasAutomationWebView(expression: string): Promi
473
473
  ));
474
474
  }
475
475
 
476
+ export function wrapCanvasAutomationScript(script: string): string {
477
+ return `(async () => {\n${script}\n})()`;
478
+ }
479
+
476
480
  export async function resizeCanvasAutomationWebView(
477
481
  width: number,
478
482
  height: number,
@@ -1501,6 +1505,12 @@ async function handleCanvasBuildWebArtifact(req: Request): Promise<Response> {
1501
1505
  bytes: result.fileSize,
1502
1506
  projectPath: result.projectPath,
1503
1507
  openedInCanvas: result.openedInCanvas,
1508
+ // `id` is the canvas node id alias used by every other add-style
1509
+ // response. It is only present when a canvas node was actually
1510
+ // created (i.e. openInCanvas was not explicitly disabled). When
1511
+ // there is no canvas node, the alias is intentionally omitted so
1512
+ // consumers can `'id' in response` to detect the build-only case.
1513
+ ...(typeof result.nodeId === 'string' ? { id: result.nodeId } : {}),
1504
1514
  nodeId: result.nodeId,
1505
1515
  url: result.url,
1506
1516
  metadata: result.metadata,
@@ -2383,10 +2393,10 @@ async function handleWorkbenchWebViewEvaluate(req: Request): Promise<Response> {
2383
2393
  if ((expression ? 1 : 0) + (script ? 1 : 0) !== 1) {
2384
2394
  return responseJson({
2385
2395
  ok: false,
2386
- error: 'Pass exactly one of "expression" (single JS expression) or "script" (multi-statement body, wrapped in an IIFE).',
2396
+ error: 'Pass exactly one of "expression" (single JS expression) or "script" (multi-statement body, wrapped in an async IIFE).',
2387
2397
  }, 400);
2388
2398
  }
2389
- const source = script ? `(() => {\n${script}\n})()` : expression;
2399
+ const source = script ? wrapCanvasAutomationScript(script) : expression;
2390
2400
 
2391
2401
  try {
2392
2402
  const value = await evaluateCanvasAutomationWebView(source);
@@ -2893,7 +2903,7 @@ async function handleSnapshotSave(req: Request): Promise<Response> {
2893
2903
  if (!name) return responseText('Missing snapshot name', 400);
2894
2904
  const snapshot = saveCanvasSnapshot(name);
2895
2905
  if (!snapshot) return responseText('Failed to save snapshot', 500);
2896
- return responseJson({ ok: true, snapshot });
2906
+ return responseJson({ ok: true, id: snapshot.id, snapshot });
2897
2907
  }
2898
2908
 
2899
2909
  async function handleContextPinsUpdate(req: Request): Promise<Response> {