pmx-canvas 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +156 -1
- package/Readme.md +13 -11
- package/dist/canvas/index.js +18 -18
- package/dist/json-render/index.css +1 -1
- package/dist/types/mcp/canvas-access.d.ts +23 -7
- package/dist/types/server/ax-state-manager.d.ts +3 -2
- package/dist/types/server/bundled-skills.d.ts +3 -3
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/dist/types/server/ext-app-lookup.d.ts +1 -3
- package/dist/types/server/operations/composites.d.ts +29 -23
- package/dist/types/server/operations/http.d.ts +2 -1
- package/dist/types/shared/ax-intent.d.ts +1 -1
- package/docs/RELEASE.md +23 -8
- package/docs/ax-host-adapter-contract.md +7 -7
- package/docs/ax-state-contract.md +7 -6
- package/docs/bun-webview-integration.md +5 -5
- package/docs/cli.md +1 -1
- package/docs/http-api.md +7 -3
- package/docs/mcp.md +121 -100
- package/docs/node-types.md +45 -35
- package/docs/plans/plan-006-mcp-tool-consolidation.md +3 -1
- package/docs/plans/plan-008-registry-finish.md +2 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +51 -0
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-06.md +1 -1
- package/docs/tech-debt-assessment-2026-07.md +135 -0
- package/package.json +1 -1
- package/skills/data-analysis/SKILL.md +3 -3
- package/skills/frontend-design/SKILL.md +3 -2
- package/skills/json-render-mcp/SKILL.md +4 -3
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/pmx-canvas/SKILL.md +20 -15
- package/skills/pmx-canvas/evals/evals.json +2 -2
- package/skills/pmx-canvas/references/ax-html-control-surface.md +3 -1
- package/skills/pmx-canvas/references/excalidraw-diagram-authoring.md +2 -2
- package/skills/pmx-canvas/references/full-reference.md +65 -62
- package/skills/pmx-canvas/references/html-primitives.md +3 -3
- package/skills/tufte-viz/SKILL.md +5 -4
- package/skills/web-artifacts-builder/SKILL.md +4 -4
- package/src/client/App.tsx +2 -1
- package/src/mcp/canvas-access.ts +115 -107
- package/src/mcp/server.ts +74 -201
- package/src/server/ax-state-manager.ts +3 -2
- package/src/server/bundled-skills.ts +3 -3
- package/src/server/canvas-schema.ts +46 -29
- package/src/server/canvas-state.ts +5 -0
- package/src/server/code-graph.ts +1 -1
- package/src/server/ext-app-lookup.ts +1 -8
- package/src/server/index.ts +1 -1
- package/src/server/intent-registry.ts +1 -1
- package/src/server/operations/composites.ts +39 -39
- package/src/server/operations/http.ts +3 -2
- package/src/server/operations/mcp.ts +7 -6
- package/src/server/operations/ops/app.ts +4 -4
- package/src/server/operations/ops/nodes.ts +5 -5
- package/src/server/operations/ops/query.ts +19 -8
- package/src/server/operations/ops/snapshots.ts +6 -6
- package/src/server/server.ts +40 -4
- package/src/shared/ax-intent.ts +1 -1
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
|
-
|
|
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();
|
|
@@ -215,19 +222,6 @@ function agentSafeFullLayoutPayload(layout: Awaited<ReturnType<CanvasAccess['get
|
|
|
215
222
|
};
|
|
216
223
|
}
|
|
217
224
|
|
|
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
225
|
function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayout']>>, pinnedIds: string[]): Record<string, unknown> {
|
|
232
226
|
const pinned = new Set(pinnedIds);
|
|
233
227
|
const nodesByType: Record<string, number> = {};
|
|
@@ -252,15 +246,19 @@ function buildSummaryFromLayout(layout: Awaited<ReturnType<CanvasAccess['getLayo
|
|
|
252
246
|
export async function startMcpServer(): Promise<void> {
|
|
253
247
|
const server = new McpServer({
|
|
254
248
|
name: 'pmx-canvas',
|
|
255
|
-
version:
|
|
249
|
+
version: readPackageVersion(),
|
|
256
250
|
});
|
|
257
251
|
resourceNotificationServer = server;
|
|
258
252
|
|
|
259
253
|
// ── Operation-registry tools (plan-005) ────────────────────────
|
|
260
|
-
//
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
254
|
+
// Standalone tools are registered from the shared operation registry for
|
|
255
|
+
// every op not folded into a composite (registerOperationTools skips
|
|
256
|
+
// composite-folded ops — see compositeFoldedOpNames in composites.ts).
|
|
257
|
+
// canvas_get_layout / canvas_get_node / canvas_add_node / canvas_update_node /
|
|
258
|
+
// canvas_remove_node were folded into canvas_query / canvas_node and removed
|
|
259
|
+
// as standalone tools in v0.3.0; they remain reachable via their composite
|
|
260
|
+
// and canvas_batch. Remaining standalone tool names and compact/full payload
|
|
261
|
+
// behavior are frozen (tests/unit/mcp-tool-freeze.test.ts, operation-parity.test.ts).
|
|
264
262
|
registerOperationTools(server, ensureCanvas);
|
|
265
263
|
|
|
266
264
|
// ── Composite (action-discriminated) tools (plan-006) ───────────
|
|
@@ -268,151 +266,12 @@ export async function startMcpServer(): Promise<void> {
|
|
|
268
266
|
// (canvas_node, canvas_render, canvas_edge, canvas_group, canvas_history,
|
|
269
267
|
// canvas_view, canvas_query, plus the AX composites). Each action dispatches
|
|
270
268
|
// to the same registered operation as its standalone tool, so behavior is
|
|
271
|
-
// identical.
|
|
272
|
-
// removed
|
|
273
|
-
// deferred to v0.
|
|
269
|
+
// identical. The legacy single-purpose tools these composites replace are
|
|
270
|
+
// removed as of v0.3.0 per docs/api-stability.md. (canvas_snapshot composite
|
|
271
|
+
// is deferred to v0.4 — its name is still held by the legacy save-snapshot
|
|
272
|
+
// tool; the 6 snapshot standalones are deprecated in v0.3.0 and fold into it.)
|
|
274
273
|
registerCompositeTools(server, ensureCanvas);
|
|
275
274
|
|
|
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
275
|
server.tool(
|
|
417
276
|
'canvas_ax_interaction',
|
|
418
277
|
'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.',
|
|
@@ -427,18 +286,25 @@ export async function startMcpServer(): Promise<void> {
|
|
|
427
286
|
.describe('Optional host/source label. Defaults to mcp.'),
|
|
428
287
|
},
|
|
429
288
|
async ({ type, sourceNodeId, payload, sourceSurface, correlationId, source }) => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
289
|
+
try {
|
|
290
|
+
const c = await ensureCanvas();
|
|
291
|
+
const result = await c.submitAxInteraction(
|
|
292
|
+
{
|
|
293
|
+
type,
|
|
294
|
+
sourceNodeId,
|
|
295
|
+
...(payload ? { payload } : {}),
|
|
296
|
+
...(sourceSurface ? { sourceSurface } : {}),
|
|
297
|
+
...(correlationId ? { correlationId } : {}),
|
|
298
|
+
},
|
|
299
|
+
{ source: source ?? 'mcp' },
|
|
300
|
+
);
|
|
301
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return {
|
|
304
|
+
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
305
|
+
isError: true,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
442
308
|
},
|
|
443
309
|
);
|
|
444
310
|
|
|
@@ -479,21 +345,28 @@ export async function startMcpServer(): Promise<void> {
|
|
|
479
345
|
source: z.enum(['agent', 'api', 'browser', 'cli', 'codex', 'copilot', 'mcp', 'sdk', 'system']).optional(),
|
|
480
346
|
},
|
|
481
347
|
async ({ kind, title, summary, outcome, ref, nodeIds, data, reactions, source }) => {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
348
|
+
try {
|
|
349
|
+
const c = await ensureCanvas();
|
|
350
|
+
const result = await c.ingestActivity(
|
|
351
|
+
{
|
|
352
|
+
kind,
|
|
353
|
+
title,
|
|
354
|
+
...(summary !== undefined ? { summary } : {}),
|
|
355
|
+
...(outcome !== undefined ? { outcome } : {}),
|
|
356
|
+
...(ref !== undefined ? { ref } : {}),
|
|
357
|
+
...(nodeIds !== undefined ? { nodeIds } : {}),
|
|
358
|
+
...(data !== undefined ? { data } : {}),
|
|
359
|
+
...(reactions !== undefined ? { reactions } : {}),
|
|
360
|
+
},
|
|
361
|
+
{ source: source ?? 'mcp' },
|
|
362
|
+
);
|
|
363
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ok: true, ...result }) }] };
|
|
364
|
+
} catch (error) {
|
|
365
|
+
return {
|
|
366
|
+
content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
|
|
367
|
+
isError: true,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
497
370
|
},
|
|
498
371
|
);
|
|
499
372
|
|
|
@@ -516,7 +389,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
516
389
|
// ── canvas_screenshot ─────────────────────────────────────────
|
|
517
390
|
server.tool(
|
|
518
391
|
'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
|
|
392
|
+
'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
393
|
{
|
|
521
394
|
format: z.enum(['png', 'jpeg', 'webp']).optional().describe('Screenshot format (default depends on Bun; png recommended)'),
|
|
522
395
|
quality: z.number().optional().describe('Optional quality for lossy formats'),
|
|
@@ -752,7 +625,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
752
625
|
'canvas://ax-pending-steering',
|
|
753
626
|
{
|
|
754
627
|
description:
|
|
755
|
-
'Adapterless AX delivery surface. `pending`: undelivered steering messages to claim and act on, then mark via
|
|
628
|
+
'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
629
|
mimeType: 'application/json',
|
|
757
630
|
},
|
|
758
631
|
async () => {
|
|
@@ -931,8 +804,8 @@ export async function startMcpServer(): Promise<void> {
|
|
|
931
804
|
// ── canvas://skills ────────────────────────────────────────
|
|
932
805
|
// Discoverability for the skill prompts bundled with the npm package
|
|
933
806
|
// (skills/<name>/SKILL.md). Before 0.1.2 these files shipped but were
|
|
934
|
-
// invisible to agents —
|
|
935
|
-
//
|
|
807
|
+
// invisible to agents — building web artifacts without the companion
|
|
808
|
+
// `web-artifacts-builder` skill led to predictable misuse.
|
|
936
809
|
// The index lists every bundled skill with its frontmatter description;
|
|
937
810
|
// individual skills are served verbatim at canvas://skills/<name>.
|
|
938
811
|
server.resource(
|
|
@@ -943,7 +816,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
943
816
|
'Index of agent skills bundled with this PMX Canvas install. Lists name, ' +
|
|
944
817
|
'description, and per-skill URI (canvas://skills/<name>). Read a specific ' +
|
|
945
818
|
'skill for workflow guidance — notably web-artifacts-builder for ' +
|
|
946
|
-
'
|
|
819
|
+
'canvas_app { action: "build-artifact" }, and pmx-canvas for the broader workbench.',
|
|
947
820
|
mimeType: 'application/json',
|
|
948
821
|
},
|
|
949
822
|
async () => {
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* • Canvas-bound (`_axState`): focus, work items, approval gates, review
|
|
9
9
|
* annotations, elicitations, mode requests, policy. Snapshotted, cleared by
|
|
10
|
-
* `
|
|
11
|
-
* (via the injected `recordMutation` / `suppressed`
|
|
10
|
+
* `canvas_view` (`clear` action), replaced by `restore`. Mutators record
|
|
11
|
+
* undo/redo history (via the injected `recordMutation` / `suppressed`
|
|
12
|
+
* callbacks).
|
|
12
13
|
* • Timeline (audit-only): agent events, evidence, steering. DB-direct, NOT in
|
|
13
14
|
* `_axState`, NOT history-recorded, NOT snapshotted, retention-bounded.
|
|
14
15
|
* • Host/session: a single host-capability row in its own table.
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Skill files ship inside the npm package under `skills/<name>/SKILL.md`
|
|
5
5
|
* but until 0.1.2 they were not discoverable to the agent — an agent
|
|
6
|
-
* calling `
|
|
7
|
-
* `skills/web-artifacts-builder/SKILL.md` prompt that documents
|
|
8
|
-
* workflow, stack choices, and gotchas.
|
|
6
|
+
* calling `canvas_app` (`build-artifact` action) had no way to find the
|
|
7
|
+
* companion `skills/web-artifacts-builder/SKILL.md` prompt that documents
|
|
8
|
+
* the workflow, stack choices, and gotchas.
|
|
9
9
|
*
|
|
10
10
|
* This module locates the bundled `skills/` directory relative to the
|
|
11
11
|
* package root (works for both repo-local development and global npm
|
|
@@ -80,7 +80,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
80
80
|
kind: 'node',
|
|
81
81
|
description: 'Freeform markdown note.',
|
|
82
82
|
endpoint: '/api/canvas/node',
|
|
83
|
-
mcpTool: '
|
|
83
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
84
84
|
fields: [
|
|
85
85
|
{ name: 'title', type: 'string', required: false, description: 'Optional node title.' },
|
|
86
86
|
{ name: 'content', type: 'string', required: false, description: 'Markdown body.' },
|
|
@@ -104,7 +104,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
104
104
|
kind: 'node',
|
|
105
105
|
description: 'Compact status indicator.',
|
|
106
106
|
endpoint: '/api/canvas/node',
|
|
107
|
-
mcpTool: '
|
|
107
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
108
108
|
fields: [
|
|
109
109
|
{ name: 'title', type: 'string', required: false, description: 'Status label.' },
|
|
110
110
|
{ name: 'content', type: 'string', required: false, description: 'Rendered status text.' },
|
|
@@ -120,7 +120,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
120
120
|
kind: 'node',
|
|
121
121
|
description: 'Agent context card container.',
|
|
122
122
|
endpoint: '/api/canvas/node',
|
|
123
|
-
mcpTool: '
|
|
123
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
124
124
|
fields: [
|
|
125
125
|
{ name: 'title', type: 'string', required: false, description: 'Optional title override.' },
|
|
126
126
|
{ name: 'content', type: 'string', required: false, description: 'Optional context body.' },
|
|
@@ -136,7 +136,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
136
136
|
kind: 'node',
|
|
137
137
|
description: 'Structured ledger/log node.',
|
|
138
138
|
endpoint: '/api/canvas/node',
|
|
139
|
-
mcpTool: '
|
|
139
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
140
140
|
fields: [
|
|
141
141
|
{ name: 'title', type: 'string', required: false, description: 'Optional title.' },
|
|
142
142
|
{ name: 'content', type: 'string', required: false, description: 'Ledger body text.' },
|
|
@@ -152,7 +152,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
152
152
|
kind: 'node',
|
|
153
153
|
description: 'Execution trace viewer.',
|
|
154
154
|
endpoint: '/api/canvas/node',
|
|
155
|
-
mcpTool: '
|
|
155
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
156
156
|
fields: [
|
|
157
157
|
{ name: 'title', type: 'string', required: false, description: 'Optional title.' },
|
|
158
158
|
{ name: 'content', type: 'string', required: false, description: 'Trace summary.' },
|
|
@@ -175,7 +175,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
175
175
|
kind: 'node',
|
|
176
176
|
description: 'Workspace file viewer.',
|
|
177
177
|
endpoint: '/api/canvas/node',
|
|
178
|
-
mcpTool: '
|
|
178
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
179
179
|
fields: [
|
|
180
180
|
{ name: 'content', type: 'string', required: true, description: 'Workspace-relative or absolute file path.' },
|
|
181
181
|
{ name: 'title', type: 'string', required: false, description: 'Optional title override.' },
|
|
@@ -193,7 +193,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
193
193
|
kind: 'node',
|
|
194
194
|
description: 'Image node backed by a path, URL, or data URI.',
|
|
195
195
|
endpoint: '/api/canvas/node',
|
|
196
|
-
mcpTool: '
|
|
196
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
197
197
|
fields: [
|
|
198
198
|
{ name: 'content', type: 'string', required: true, description: 'Image path, URL, or data URI.', aliases: ['path'] },
|
|
199
199
|
{ name: 'title', type: 'string', required: false, description: 'Optional title override.' },
|
|
@@ -219,7 +219,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
219
219
|
kind: 'node',
|
|
220
220
|
description: 'Persisted webpage snapshot with server-side fetch and refresh.',
|
|
221
221
|
endpoint: '/api/canvas/node',
|
|
222
|
-
mcpTool: '
|
|
222
|
+
mcpTool: 'canvas_node (action:"add")',
|
|
223
223
|
fields: [
|
|
224
224
|
{ name: 'url', type: 'string', required: true, description: 'HTTP(S) URL to fetch and cache.', aliases: ['content'] },
|
|
225
225
|
{ name: 'title', type: 'string', required: false, description: 'Optional title override.' },
|
|
@@ -314,7 +314,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
314
314
|
kind: 'node',
|
|
315
315
|
description: 'Hosted iframe/app node.',
|
|
316
316
|
endpoint: '/api/canvas/node',
|
|
317
|
-
mcpTool: '
|
|
317
|
+
mcpTool: 'canvas_app (action:"open-mcp-app")',
|
|
318
318
|
fields: [
|
|
319
319
|
{ name: 'title', type: 'string', required: false, description: 'App title.' },
|
|
320
320
|
{ name: 'content', type: 'string', required: false, description: 'Optional inline content.' },
|
|
@@ -333,7 +333,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
333
333
|
kind: 'virtual-node',
|
|
334
334
|
description: 'Tool-backed hosted app opened from an external MCP server, such as Excalidraw.',
|
|
335
335
|
endpoint: '/api/canvas/mcp-app/open',
|
|
336
|
-
mcpTool: '
|
|
336
|
+
mcpTool: 'canvas_app (action:"open-mcp-app")',
|
|
337
337
|
fields: [
|
|
338
338
|
{ name: 'toolName', type: 'string', required: true, description: 'Tool name on the external MCP server.' },
|
|
339
339
|
{ name: 'transport', type: '{ type: "stdio", command, args? } | { type: "http", url, headers? }', required: true, description: 'External MCP transport definition.' },
|
|
@@ -347,8 +347,8 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
347
347
|
title: 'Excalidraw Diagram',
|
|
348
348
|
},
|
|
349
349
|
notes: [
|
|
350
|
-
'For Excalidraw specifically, prefer
|
|
351
|
-
'The CLI convenience command `external-app add --kind excalidraw` maps to this built-in Excalidraw preset; MCP
|
|
350
|
+
'For Excalidraw specifically, prefer canvas_app (action:"diagram") because it fills in the built-in transport, toolName, and checkpoint wiring.',
|
|
351
|
+
'The CLI convenience command `external-app add --kind excalidraw` maps to this built-in Excalidraw preset; MCP canvas_app (action:"open-mcp-app") is the lower-level transport form.',
|
|
352
352
|
],
|
|
353
353
|
},
|
|
354
354
|
{
|
|
@@ -356,7 +356,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
356
356
|
kind: 'node',
|
|
357
357
|
description: 'Canvas group frame.',
|
|
358
358
|
endpoint: '/api/canvas/group',
|
|
359
|
-
mcpTool: '
|
|
359
|
+
mcpTool: 'canvas_group (action:"create")',
|
|
360
360
|
fields: [
|
|
361
361
|
{ name: 'title', type: 'string', required: false, description: 'Group title.' },
|
|
362
362
|
{ name: 'childIds', type: 'string[]', required: false, description: 'Initial child node IDs.' },
|
|
@@ -373,7 +373,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
373
373
|
kind: 'virtual-node',
|
|
374
374
|
description: 'Native structured UI panel rendered from a validated json-render spec.',
|
|
375
375
|
endpoint: '/api/canvas/json-render',
|
|
376
|
-
mcpTool: '
|
|
376
|
+
mcpTool: 'canvas_render (action:"add-json-render")',
|
|
377
377
|
fields: [
|
|
378
378
|
{ name: 'title', type: 'string', required: false, description: 'Optional rendered node title; inferred from the root element when omitted.' },
|
|
379
379
|
{ name: 'spec', type: 'JsonRenderSpec | JsonRenderElement', required: true, description: 'Complete {root, elements} json-render spec, or a legacy single bare component object with a type field.' },
|
|
@@ -406,7 +406,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
406
406
|
kind: 'virtual-node',
|
|
407
407
|
description: 'Native chart node backed by the json-render chart catalog.',
|
|
408
408
|
endpoint: '/api/canvas/graph',
|
|
409
|
-
mcpTool: '
|
|
409
|
+
mcpTool: 'canvas_render (action:"add-graph")',
|
|
410
410
|
fields: [
|
|
411
411
|
{
|
|
412
412
|
name: 'graphType',
|
|
@@ -467,7 +467,7 @@ const CANVAS_CREATE_TYPES: CanvasCreateTypeSchema[] = [
|
|
|
467
467
|
kind: 'virtual-node',
|
|
468
468
|
description: 'Bundled single-file HTML artifact that can open as an embedded canvas node.',
|
|
469
469
|
endpoint: '/api/canvas/web-artifact',
|
|
470
|
-
mcpTool: '
|
|
470
|
+
mcpTool: 'canvas_app (action:"build-artifact")',
|
|
471
471
|
fields: [
|
|
472
472
|
{ name: 'title', type: 'string', required: true, description: 'Artifact title used for default paths.' },
|
|
473
473
|
{ 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'] },
|
|
@@ -556,19 +556,36 @@ export function describeCanvasSchema(): {
|
|
|
556
556
|
htmlPrimitives: listHtmlPrimitiveDescriptors(),
|
|
557
557
|
mcp: {
|
|
558
558
|
tools: [
|
|
559
|
-
|
|
560
|
-
'
|
|
561
|
-
'
|
|
562
|
-
'
|
|
563
|
-
'
|
|
564
|
-
'
|
|
565
|
-
'
|
|
566
|
-
'
|
|
567
|
-
'
|
|
568
|
-
'
|
|
569
|
-
'
|
|
570
|
-
'
|
|
571
|
-
'
|
|
559
|
+
// 15 composites
|
|
560
|
+
'canvas_node',
|
|
561
|
+
'canvas_render',
|
|
562
|
+
'canvas_edge',
|
|
563
|
+
'canvas_group',
|
|
564
|
+
'canvas_history',
|
|
565
|
+
'canvas_view',
|
|
566
|
+
'canvas_query',
|
|
567
|
+
'canvas_webview',
|
|
568
|
+
'canvas_app',
|
|
569
|
+
'canvas_ax_state',
|
|
570
|
+
'canvas_ax_work',
|
|
571
|
+
'canvas_ax_gate',
|
|
572
|
+
'canvas_ax_timeline',
|
|
573
|
+
'canvas_ax_delivery',
|
|
574
|
+
'canvas_intent',
|
|
575
|
+
// 6 standalones
|
|
576
|
+
'canvas_batch',
|
|
577
|
+
'canvas_pin_nodes',
|
|
578
|
+
'canvas_invoke_command',
|
|
579
|
+
'canvas_ax_interaction',
|
|
580
|
+
'canvas_ingest_activity',
|
|
581
|
+
'canvas_screenshot',
|
|
582
|
+
// 6 snapshot tools (deprecated; fold into canvas_snapshot composite in v0.4)
|
|
583
|
+
'canvas_snapshot',
|
|
584
|
+
'canvas_list_snapshots',
|
|
585
|
+
'canvas_restore',
|
|
586
|
+
'canvas_delete_snapshot',
|
|
587
|
+
'canvas_gc_snapshots',
|
|
588
|
+
'canvas_diff',
|
|
572
589
|
],
|
|
573
590
|
resources: ['canvas://schema'],
|
|
574
591
|
nodeTypeRouting: buildMcpNodeTypeRouting(nodeTypes),
|
|
@@ -571,6 +571,11 @@ class CanvasStateManager {
|
|
|
571
571
|
private _db: import('bun:sqlite').Database | null = null;
|
|
572
572
|
private _saveTimer: ReturnType<typeof setTimeout> | null = null;
|
|
573
573
|
|
|
574
|
+
/** Workspace root backing persistence and workspace-relative path resolution. */
|
|
575
|
+
get workspaceRoot(): string {
|
|
576
|
+
return this._workspaceRoot;
|
|
577
|
+
}
|
|
578
|
+
|
|
574
579
|
/** Set the workspace root to enable auto-persistence. */
|
|
575
580
|
setWorkspaceRoot(workspaceRoot: string): void {
|
|
576
581
|
this.close();
|
package/src/server/code-graph.ts
CHANGED
|
@@ -113,7 +113,7 @@ function resolveImportPath(specifier: string, fromFilePath: string): string | nu
|
|
|
113
113
|
// Check if it looks like a relative project path (contains / and no @)
|
|
114
114
|
if (!specifier.includes('/') || specifier.startsWith('@')) return null;
|
|
115
115
|
// For non-relative paths, try workspace-relative resolution
|
|
116
|
-
const wsRoot =
|
|
116
|
+
const wsRoot = canvasState.workspaceRoot;
|
|
117
117
|
return tryResolveWithExtensions(resolve(wsRoot, specifier));
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
* Resolution order:
|
|
10
10
|
* 1. The direct prefixed form (`ext-app-<toolCallId>` if not already
|
|
11
11
|
* prefixed, otherwise `toolCallId` as-is).
|
|
12
|
-
* 2.
|
|
13
|
-
* v0.1.4 and still on disk. Remove this fallback in v0.2.x.
|
|
14
|
-
* 3. A scan of the layout for any `mcp-app` ext-app node carrying that
|
|
12
|
+
* 2. A scan of the layout for any `mcp-app` ext-app node carrying that
|
|
15
13
|
* `toolCallId` in its data.
|
|
16
14
|
*/
|
|
17
15
|
import type { CanvasNodeState } from './canvas-state.js';
|
|
@@ -30,11 +28,6 @@ export function findCanvasExtAppNodeId(
|
|
|
30
28
|
: `ext-app-${toolCallId}`;
|
|
31
29
|
if (source.getNode(directId)) return directId;
|
|
32
30
|
|
|
33
|
-
const legacyDirectId = `ext-app-${toolCallId}`;
|
|
34
|
-
if (legacyDirectId !== directId && source.getNode(legacyDirectId)) {
|
|
35
|
-
return legacyDirectId;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
31
|
for (const node of source.listNodes()) {
|
|
39
32
|
if (
|
|
40
33
|
node.type === 'mcp-app' &&
|