| name | pmx-canvas |
| description | Spatial canvas workbench for visual thinking — nodes, edges, groups on an infinite 2D canvas with pan/zoom, minimap, and real-time sync. Use this skill whenever you need to lay out information spatially: investigation boards, architecture diagrams, dependency maps, task plans, status dashboards, file relationship views, or any scenario where a flat list or text wall isn't enough. Also use when the user mentions "canvas", "board", "diagram", "spatial layout", "visual map", "node graph", or wants to see how things connect. The canvas is your extended working memory — pin nodes to curate context, read spatial arrangement to understand intent.
|
PMX Canvas - Agent Skill
PMX Canvas is a spatial canvas workbench you control through MCP tools or HTTP API. It renders an
infinite 2D canvas in the browser with nodes, edges, groups, pan/zoom, and a minimap. State lives
on the server and survives browser refresh.
The canvas is your extended working memory. Humans pin nodes to curate context; you read that
curation through MCP resources. Spatial arrangement is communication — proximity means
relatedness, clusters imply grouping, reading order (top-left to bottom-right) implies sequence.
When to Use
The canvas is agnostic about what you do with it. Reach for it whenever a
flat conversation, a list, or a single document hides the relationships
between pieces of information. The total reach of any session is the union
of pmx-canvas's own node types and whatever your harness already has access
to — MCP servers, MCP apps, shell commands and CLIs, files in the working
directory, web fetch, anything else in your toolbelt. The canvas does not
care where the data came from; it cares about getting it on the surface as
the right node type.
The README has a non-exhaustive list of example use cases (idea generation,
validation, research, analysis, mind mapping, investigation boards,
architecture diagrams, status dashboards, comparison views, plus whatever
your toolbelt unlocks). This skill stays focused on the operational
mechanics. If a flat list or text wall is not enough to hold the
relationships you're working with, the canvas is the right tool — the rest
of this document is how to drive it.
When the connected tool is unfamiliar
When the human asks you to use a data source or tool you have not used
before — an MCP server, an MCP app, a CLI, a script, an arbitrary file
tree:
- List the tools / commands available; sample one or two outputs to see
the actual shape.
- Decide which canvas node type best matches each output:
- Long-form / narrative results →
markdown
- Structured records, tables, dashboards →
json-render or graph
- Rich reusable communication artifacts →
html-primitive
- Interactive tool surfaces with their own UI →
mcp-app (open with
canvas_open_mcp_app)
- Local source files →
file (live-watched)
- URLs that need cached fetches →
webpage
- Stream of state events →
status or ledger
- Propose the mapping to the human before bulk-creating nodes; let them
confirm or adjust before you commit a layout.
Starting the Canvas
If this skill is installed before the pmx-canvas command exists, install the project first. See
references/installing-pmx-canvas.md for local development, npm/global install, and MCP config
options.
The canvas auto-starts on first MCP tool call when running in MCP mode (pmx-canvas --mcp).
For manual start:
pmx-canvas
pmx-canvas --no-open
pmx-canvas --port=8080
pmx-canvas --demo
pmx-canvas --theme=light
pmx-canvas --version
--theme accepts dark (default), light, or high-contrast. Same value can be set via
the PMX_CANVAS_THEME environment variable, or toggled live in the browser toolbar.
Start the canvas once per session, then reuse it. Use --no-open when running as an agent — the
human can open the browser URL themselves.
Daemon mode (long-running background server)
When you need the canvas to outlive the current shell or agent session — e.g. so a follow-up
agent run can attach to the same state — start it as a daemon:
pmx-canvas serve --daemon --no-open --wait-ms=20000
pmx-canvas serve status
pmx-canvas serve stop
serve --daemon writes a pid file (.pmx-canvas/daemon-<port>.pid) and a log file
(.pmx-canvas/daemon-<port>.log); the wait flag blocks until /health returns OK so a script
can rely on the server being responsive when the command returns. serve stop reads the pid
file, sends SIGTERM, and cleans up on exit.
Browser Workflows
The browser is not just a passive view. Human interactions on the canvas persist back to the
server and become part of the authoritative canvas state.
- Double-click empty canvas — create a markdown note at that position
- Shift+drag on empty canvas — lasso-select multiple nodes
- Selection bar actions — when nodes are selected, the browser exposes
Pin as context,
Group, Connect, and Clear
- Right-click a node — open the node context menu for focus, collapse, pinning, connecting,
refresh/open actions, and other type-specific operations
- Right-click a group node — recolor the group using preset swatches or a custom color picker,
and ungroup its children
- Drag-and-drop files or URLs — add file, image, markdown, or webpage nodes directly
- Paste URLs — create webpage nodes from the clipboard
Use browser interactions when the human is actively curating spatial layout. Use MCP or the CLI
when you need deterministic scripted changes or you are acting without a visible browser.
Agent CLI
PMX Canvas also ships an agent-native CLI that talks to the running HTTP server and returns JSON.
Use it when MCP is not available but you still want structured, scriptable canvas operations.
pmx-canvas --help
pmx-canvas serve --daemon --no-open
pmx-canvas serve status
pmx-canvas serve stop
pmx-canvas layout
pmx-canvas status
pmx-canvas node add --type markdown --title "Plan"
pmx-canvas node add --type webpage --url https://example.com/docs
pmx-canvas node add --type graph --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y
pmx-canvas node add --type graph --graphType bar --data '[{"x":"a","y":1}]' --xKey x --yKey y
pmx-canvas graph add --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y
pmx-canvas external-app add --kind excalidraw --title "Diagram"
pmx-canvas node add --help --type webpage --json
pmx-canvas node schema --type json-render --component Table --summary
pmx-canvas node list --type file --ids
pmx-canvas edge add --from node-a --to node-b --type depends-on
pmx-canvas search "auth"
pmx-canvas open
pmx-canvas arrange --layout flow
pmx-canvas focus <node-id> --no-pan
pmx-canvas fit --width 1440 --height 900
pmx-canvas screenshot --output ./canvas.png
pmx-canvas json-render --schema --summary
pmx-canvas json-render --example --component Table
pmx-canvas node add --type markdown --title "Long doc" --strict-size
pmx-canvas node add --type graph --graphType pie --data-file metrics.json --show-legend false --show-labels false
pmx-canvas node update <node-id> --spec-file ./dashboard.json
pmx-canvas validate spec --type json-render --spec-file ./dashboard.json --summary
pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx --deps recharts --include-logs
pmx-canvas node list --type web-artifact --summary
pmx-canvas node list --type external-app --summary
pmx-canvas pin --list
pmx-canvas snapshot save --name "before-refactor"
pmx-canvas code-graph
pmx-canvas spatial
CLI command groups
node add|list|get|update|remove — manage nodes
node schema — inspect running-server create schemas and canonical examples, with --summary, --field, and --component filters
graph add — convenience alias for graph nodes; node add --type graph remains the canonical form
- Graph CLI fields accept both kebab-case flags and camelCase schema names, e.g.
--graph-type/--graphType, --x-key/--xKey, and --bar-color/--barColor.
- Graph CLI height flags are split: use
--node-height/--nodeHeight for the
canvas frame and --chart-height for the chart content. CLI --height
remains a frame-height compatibility alias.
edge add|list|remove — manage edges
- Search-based edge selectors must be specific enough to resolve exactly one node. Queries like
"DVT O3" can be ambiguous; prefer the full visible title such as "DVT O3 — GitOps".
search, layout, status, arrange, focus — inspect and navigate the canvas. Prefer
focus --no-pan when you only need to select/raise a node without hijacking the human's camera.
fit [id ...] — set the server viewport to fit the whole canvas or selected nodes before screenshots or whole-board review
screenshot --output <path> — top-level shortcut for webview screenshot; supports --format png|jpeg|webp and --quality
json-render --schema|--examples — inspect the json-render component catalog with --component/--field filters; same data as node schema --type json-render in a more direct shape
--strict-size (alias --scroll-overflow) on node add/node update — keep explicit width/height fixed and scroll overflowing content instead of letting the renderer auto-fit. Useful for long markdown, dense webpages, and dashboards that should fit a tile-sized frame.
--show-legend false / --show-labels false on node add --type graph and graph add — hide chart legends and pie slice labels for compact graph nodes in tile-style boards.
open — open the current workbench in the browser
pin --list|--clear|<ids...> — manage context pins
undo, redo, history — time travel
snapshot save|list|restore|delete — manage snapshots
group create|add|remove — manage groups
clear --yes — destructive clear with explicit confirmation
validate spec — validate json-render specs and graph payloads without creating nodes
web-artifact build — build bundled React/Tailwind HTML artifacts; use --deps for extra packages and --include-logs only when raw logs are useful
external-app add --kind excalidraw — create the hosted Excalidraw preset; response includes id and nodeId aliases for the same canvas node
serve status|stop — inspect and stop daemonized servers started with serve --daemon
code-graph, spatial — analysis commands
Current caveat:
mcp-app grouping is not fully uniform yet. Web artifact app nodes have grouped reliably, but
Excalidraw app nodes have shown inconsistent group add behavior and weaker rediscoverability
through search later in the session. When you plan to curate an app-heavy comparison area,
capture node IDs immediately after creation and verify membership with node get --summary,
layout --summary, or the browser selection state instead of relying on search alone.
- App-like nodes persist as
type: "mcp-app" internally but serialized results include kind:
web-artifact, external-app, or mcp-app. Prefer node list --type web-artifact or
node list --type external-app when you need the operational subtype.
- Generic
pmx-canvas node add --type mcp-app is intentionally not supported because app nodes
need app/session metadata. Use pmx-canvas web-artifact build for bundled React artifacts or
pmx-canvas external-app add --kind excalidraw for the Excalidraw preset.
- For local
image nodes on macOS, iCloud/OneDrive cloud-only placeholder files are rejected with
a download-first hint. Download the image locally before adding it to the canvas.
The CLI targets http://localhost:4313 by default. Override with PMX_CANVAS_URL or
PMX_CANVAS_PORT when the canvas is running elsewhere.
Core Concepts
Node Types
| Type | Purpose | When to use |
|---|
markdown | Rich formatted content | Explanations, documentation, notes, findings |
status | Compact color-coded indicator | Progress tracking, build/test results, task state |
file | Live file viewer (auto-watches) | Show source code with live updates on file change |
image | Image display | Screenshots, diagrams, charts |
context | Context card | Key context the human should see |
ledger | Log/ledger viewer | Structured log data, audit trails |
trace | Trace/timeline viewer | Execution traces, timelines |
mcp-app | Hosted app/embed frame | Tool-backed MCP apps or external app content; not generic CLI-created notes |
json-render | Native structured UI panel | Dashboards, forms, tables, interactive layouts from json-render specs |
graph | Native chart panel | Line, bar, pie, area, scatter, radar, stacked-bar, and composed charts rendered inside the canvas |
html | Sandboxed HTML+JS document | Self-contained HTML with optional inline <script> and CDN imports rendered in a sandbox-restricted iframe; canvas theme tokens are auto-injected |
group | Spatial container/frame | Visually group related nodes together |
prompt | Prompt thread root | Canvas-native prompt entry points for agent conversations. Internal type — surfaces in canvas://layout for thread rendering but is not created via the public canvas_add_node API. Don't try to add one directly. |
response | Prompt reply / streamed answer | Agent responses linked to prompt threads. Same internal-only restriction as prompt. |
Edge Types
| Type | Purpose | Example label |
|---|
flow | Sequential/directional | "then", "calls", "triggers" |
depends-on | Dependency | "requires", "blocks" |
relation | General relationship | "related to", "similar to" |
references | Cross-reference | "see also", "documented in" |
Edges support style (solid/dashed/dotted) and animated flag. Always use descriptive labels.
Edge direction convention: from is the source/dependent, to is the target/dependency.
So from: A, to: B, type: "depends-on" means "A depends on B." For flow edges, the arrow
points from from to to, indicating sequence or data flow direction.
Style conventions: Use solid for active/satisfied relationships, dashed for blocked or
pending dependencies, and dotted for weak/optional relationships. Use animated: true to
draw visual attention to critical paths.
Colors (Semantic)
Use color consistently to convey meaning:
- Green (
#22c55e) — success, done, healthy
- Yellow (
#eab308) — in progress, warning, attention needed
- Red (
#ef4444) — error, blocked, failing
- Blue (
#3b82f6) — informational, neutral highlight
- Gray (
#6b7280) — queued, pending, inactive, not yet started
- Purple (
#a855f7) — special, notable, review needed
MCP Tools Reference
Node Operations
MCP node-type routing:
| Node category | MCP creation tool |
|---|
Basic nodes (markdown, status, context, ledger, trace, file, image, webpage) | canvas_add_node |
json-render | canvas_add_json_render_node |
graph | canvas_add_graph_node |
html-primitive | canvas_add_html_primitive |
html | canvas_add_html_node |
web-artifact | canvas_build_web_artifact |
external-app / tool-backed mcp-app | canvas_open_mcp_app |
group | canvas_create_group |
If a node type is rejected by canvas_add_node, call canvas_describe_schema and read
mcp.nodeTypeRouting; do not keep retrying the generic tool.
canvas_add_node — Add a node to the canvas
type (required): basic node type only; structured/app/group nodes use the routing table above
title: short, scannable title
content: for most types, this is markdown text. For file type, pass the file path
(e.g., "src/auth/login.ts") — the server auto-loads the file content and watches for changes.
For image type, pass a file path, URL, or data URI.
path: compatibility alias for image paths only; prefer content for new image calls
x, y: position (auto-placed if omitted — prefer omitting for auto-layout)
width, height: dimensions (sensible defaults provided)
color: semantic color
metadata: arbitrary JSON
- Returns:
{ id: "<node-id>" } — capture this ID for edges and groups
canvas_update_node — Update an existing node
id (required): node to update
- Any of:
title, content, x, y, width, height, collapsed, arrangeLocked, data
- For
json-render, pass spec to update the rendered spec in place while preserving node ID, edges, pins, and position
- For
graph, pass graph fields such as graphType, data, xKey, yKey, color, and chartHeight to rebuild the chart in place; use height/nodeHeight for frame geometry and chartHeight for chart content in CLI flows
- Use to update status nodes as work progresses
canvas_remove_node — Remove a node and all its connected edges
id (required): node to remove
- Clean up nodes that are no longer relevant
canvas_remove_annotation — Remove a human-drawn annotation
id (required): annotation to remove
- Use when context gives you the annotation ID; use WebView first if you need to identify a mark by shape or location.
canvas_get_node — Get a single node's full data
id (required): node to retrieve
canvas_refresh_webpage_node — Re-fetch the URL stored on a webpage node
-
id (required): webpage node to refresh
-
Optional url: replace the stored URL before refreshing (use when the human moved the page)
-
Returns the refreshed node with updated pageTitle and cached extracted text
-
Use this when a saved canvas is reopened and the agent needs fresh page content without
losing the node's identity, position, or pins. Example flow:
canvas_add_node({ type: 'webpage', url: 'https://example.com/docs' })
canvas_refresh_webpage_node({ id: 'node-abc' })
canvas_add_json_render_node — Add a native json-render node
- Required:
spec; title is optional and inferred from the root element when omitted
- Prefer a complete json-render object with
root, elements, and optional state
- Legacy bare component specs like
{ type: "Badge", props: {...} } are accepted and wrapped into a one-element document for compatibility
- Use this when you want a structured UI panel rendered directly inside PMX Canvas
- For shadcn
Badge, prefer props.text with variants default, secondary, destructive, or
outline. Legacy props.label and status variants (success, info, warning, error,
danger) are normalized for saved-spec compatibility.
canvas_add_graph_node — Add a native graph/chart node
- Required:
graphType, data
- Supports
line, bar, pie, area, scatter, radar, stacked-bar, and composed
graph types (aliases accepted)
- Use
xKey/yKey for line, bar, area, and scatter graphs
- Use
zKey for scatter bubble size
- Use
nameKey/valueKey for pie graphs
- Use
axisKey plus metrics for radar graphs
- Use
series for stacked-bar graphs
- Use
barKey/lineKey plus optional barColor/lineColor for composed graphs
- Use
nodeHeight for the canvas frame height and height for chart content height
- Uses the native json-render chart catalog under the hood
canvas_build_web_artifact — Build and optionally open a bundled web artifact
- Required:
title, appTsx (source string contents, not a file path)
- CLI
--app-file reads a file before calling the same build path; MCP callers must pass the source contents
- Cold builds commonly take 45-60 seconds; use a long client timeout such as 300000 ms or more
- Returns both
id and nodeId for the created artifact node when openInCanvas is true
ID extraction for mixed tool responses:
- Most add-style tools return a flat
id; web artifacts return id plus nodeId; snapshots return id plus nested snapshot.id.
- Defensive extractor:
const getId = (r) => r.id ?? r.nodeId ?? r.snapshot?.id;
canvas_open_mcp_app — Open a tool-backed external MCP app node
- Required:
toolName, transport
transport is either { type: "stdio", command, args?, cwd?, env? } or { type: "http", url, headers? }
- This is lower-level than
pmx-canvas external-app add --kind excalidraw; use canvas_add_diagram for the built-in Excalidraw preset
canvas_pin_nodes — Set, add, or remove pinned context nodes
- Use
{ nodeIds: [...] }; the field is nodeIds, not ids
canvas_diff — Compare current canvas state with a saved snapshot
- Requires
{ snapshot: "<snapshot-id-or-name>" }; there is no implicit previous-snapshot default
canvas_describe_schema — Inspect the running server's create schemas and canonical examples
- Use this before generating structured payloads when you need the authoritative current shape
- Read
mcp.nodeTypeRouting to choose the right MCP creation tool for each node category
canvas_validate_spec — Validate a json-render spec or graph payload without creating a node
- Returns the normalized json-render spec the server would accept
- Use this when you want a dry run before creating a
json-render or graph node
canvas_fit_view — Fit viewport to all nodes or selected nodes
- Optional:
width, height, padding, maxScale, nodeIds
- Use before screenshot workflows or whole-board review so the server viewport matches the intended camera
Batch graph creation
- Use
graph.add inside canvas_batch / pmx-canvas batch when you need a graph node as part of
a larger one-shot build.
- It accepts the same shape as
canvas_add_graph_node: graphType, data, optional title,
xKey, yKey, zKey, nameKey, valueKey, axisKey, metrics, series, barKey,
lineKey, aggregate, color, barColor, lineColor, height, x, y, width, and
nodeHeight.
- In batch/MCP/HTTP payloads,
height is chart content height and nodeHeight is the canvas frame height.
Edge Operations
canvas_add_edge — Connect two nodes
from, to (required): source and target node IDs
fromSearch, toSearch: optional search-based selectors when you do not have IDs. Each search
query must resolve to exactly one node or the edge creation fails with an ambiguity error.
type: flow, depends-on, relation, or references (default: relation)
label: descriptive relationship label
style: solid, dashed, or dotted
animated: boolean for visual emphasis
canvas_remove_edge — Remove a connection
id (required): edge to remove
Layout & Navigation
canvas_get_layout — Get full canvas state (all nodes, edges, viewport)
- Use to understand current canvas state before making changes
canvas_arrange — Auto-arrange all nodes
layout: grid (default), column, or flow
- Use
grid for dashboards and architecture overviews, column for vertical lists, flow
for horizontal sequences and dependency chains
- Call after adding multiple nodes
- For tiered/layered layouts (e.g., gateway → services → data stores), use
canvas_update_node
with explicit x/y coordinates after auto-arrange to fine-tune the topology
canvas_focus_node — Pan viewport to center on a specific node
id (required): node to focus
- Good for drawing the human's attention
- Avoid focusing every node in a batch. Focus only the final result or use CLI
focus --no-pan
when the goal is selection/raising without camera movement.
Groups
canvas_create_group — Create a visual container
title: group label
childIds: array of node IDs to include
color: group border/background color
- Auto-sizes to fit children
canvas_group_nodes — Add nodes to an existing group
groupId, childIds (required)
canvas_ungroup — Release children from a group
groupId (required): group to dissolve
Group Layout Guidance
Use groups as spacious semantic regions, not as tight containers.
- Size the child nodes first, especially
graph, json-render, mcp-app, image, and webpage
nodes whose rendered content may need more height than their visible title suggests.
- Give every group generous interior padding. Reserve extra top padding for the group header, then
keep children clear of the frame edges so headers, glow, resize handles, and node chrome do not
visually collide.
- If creating a group manually, compute its frame from the final child bounds plus padding. If the
group exists first, expand it before adding large children rather than shrinking children to fit.
- Use groups to label major regions of a board. Avoid wrapping every small relationship; too many
tight groups make the canvas harder to read than no groups.
- Keep edges local to a group where possible. Long cross-board edges can look like they come from
nowhere; use a nearby bridge/context node or split the relationship into shorter labeled edges.
- After grouping, verify the result in
canvas_get_layout or the browser: child nodes should be
fully inside the group with padding, visible nodes should not overlap, and group headers should
not cover content.
- If a group makes important content less visible, enlarge the group, split it into clearer
regions, or remove the group. Visibility is more important than preserving a frame.
Grouped Comparison Boards
Use groups as named comparison areas, not just visual boxes.
- Create the comparison frame first with
canvas_create_group or pmx-canvas group create, then
add charts, artifacts, and diagrams into that area deliberately.
- Prefer graph nodes for fast capability demos and side-by-side comparisons. They are lightweight,
validate quickly, and are easier to regenerate.
- Prefer web artifacts when the board needs a richer narrative UI, custom interaction, or a more
polished presentation layer than a graph or json-render node can provide.
- Use Excalidraw for sketching and flow diagrams, but treat it as less reliable than web-artifact
app nodes for grouping and rediscovery until
mcp-app grouping parity is fixed.
- Native node types are still the most agent-friendly. Graph nodes are the strongest comparison
primitive today, web artifacts are good but heavier, and Excalidraw / other
mcp-app nodes are
useful but still the weakest operationally for create, rediscover, group, and reconnect flows.
- Leave larger spacing between major regions than you think you need. The spatial analyzer still
tends to read dense boards as one giant cluster unless groups and gaps are both clear.
- If you are expanding a board incrementally, verify each add-to-group step instead of assuming
the node joined the area. Comparison workflows depend on reliable “add this thing to the region
I’m already building.”
Current product caveats for grouped comparison boards:
mcp-app grouping parity is inconsistent. Web artifacts have grouped cleanly; Excalidraw has
not always behaved the same way.
- Search/discoverability for external app nodes can degrade over time in-session, so node IDs are
safer than title-based rediscovery for follow-up grouping or focus operations.
mcp-app nodes are less inspectable than native nodes. For graph nodes you can reason from
structured config, but app nodes often only tell you that an app exists unless you also inspect
nearby markdown, file, or graph context.
- Long-running web artifact builds can exceed a short command timeout. When using them in an
agent workflow, prefer progress-aware handling and avoid assuming a timeout means failure.
Search & Discovery
canvas_search — Find nodes by title or content keywords
query (required): search text
- Returns matches with relevance ranking and content snippets
- Use instead of parsing full layout when looking for specific nodes
Context Pinning
canvas_pin_nodes — Manage pinned context
nodeIds (required): array of node IDs
mode: set (replace all pins), add (add to pins), remove (remove from pins)
- Pinned nodes are the primary human-to-agent communication channel
- When a human pins nodes in the browser, they're telling you "pay attention to these"
- Best default pin set for agent collaboration: one intent-setting markdown node plus 1-3 concrete
output nodes
- Graph, file, and markdown pins usually carry richer usable context than
mcp-app pins
- Artifact and Excalidraw pins still matter as intent signals, but pair them with a markdown or
graph pin when you want the agent to understand what is inside the app, not just that it matters
History & Snapshots
canvas_undo — Undo the last canvas mutation
canvas_redo — Redo the last undone mutation
canvas_snapshot — Save a named snapshot to disk
name (required): descriptive snapshot name (e.g., "before-refactor")
- Returns
{ ok, id, snapshot }; the flat id is an alias for snapshot.id
canvas_restore — Restore canvas from a saved snapshot
id: snapshot to restore
canvas_diff — Compare current canvas against a saved snapshot
- Shows added, removed, and modified nodes/edges
- Useful for reviewing what changed during a work session
Canvas Management
canvas_clear — Remove all nodes and edges
- Always call
canvas_snapshot first to save a backup before clearing
- This is irreversible without a prior snapshot
Browser Automation (WebView)
The canvas exposes a headless browser session over MCP for self-inspection and
automated screenshotting. Use this when you want to (a) verify what the live
canvas actually looks like after a sequence of mutations, (b) capture an image
of a freshly-built artifact for the human to review, or (c) drive arbitrary
JavaScript inside the workbench page.
The WebView automation runs on Bun's WebKit-based WebView (macOS) or a headless
Chromium fallback (Linux). It does not open a visible window; it's an
additional headless renderer attached to the same canvas server, so all five
tools below operate on the live canvas state.
canvas_webview_status — Inspect the current automation session
- Returns
{ supported, active, backend, viewportWidth, viewportHeight, url, lastError }
- Call before
start to check whether a session is already alive
canvas_webview_start — Start (or replace) the automation session
- Optional:
backend (webkit macOS-only, or chrome), width, height
- The session opens
/workbench at the canvas URL, waits for the SPA to
hydrate, and reports back via canvas_webview_status
canvas_webview_stop — Tear down the automation session
canvas_evaluate — Run JavaScript inside the workbench page and return the result
-
Required: exactly one of expression (single JS expression) or script (multi-statement body)
-
script is wrapped in an async IIFE, so top-level await works inside script bodies
-
Useful for asserting DOM state after a sequence of canvas mutations
-
Do not use fetch() inside canvas_evaluate to call PMX HTTP APIs; WebView security/CORS
restrictions can block those requests. Use the matching MCP tools instead.
-
Example: read the count of rendered .canvas-node elements:
canvas_evaluate({ expression: 'document.querySelectorAll(".canvas-node").length' })
Useful workbench selectors:
- Nodes:
.canvas-node, .canvas-node.active, .canvas-node.context-pinned, .canvas-node.group-node
- Node internals:
.node-title, .node-titlebar, .node-body, .node-type-badge, .node-controls
- Annotations:
.annotation-layer path renders human-drawn freehand ink. Use WebView
to inspect or screenshot annotation shapes; MCP/context resources only expose compact
annotation target summaries, not the raw visual shape. Humans can remove marks with
the eraser toolbar button; agents can remove a known annotation ID with
canvas_remove_annotation.
- Canvas chrome:
.hud-layer, .canvas-toolbar, .connection-dot, .canvas-bootstrap-card
- Nodes do not expose stable
data-node-id attributes. Use canvas_get_layout, canvas_search, or MCP resource data for exact node IDs.
Async script example:
canvas_evaluate({
script: 'const title = await Promise.resolve(document.title); return title;',
})
canvas_resize — Change the WebView viewport
- Required:
width, height
- Use before
canvas_screenshot when the human needs a specific aspect ratio
canvas_screenshot — Capture a PNG of the current workbench
- Optional:
format (png default), fullPage (boolean)
- Returns both an MCP image payload (renderable inline by capable agents) and
a path under
.pmx-canvas/screenshots/ so the human can view the file
- Pair with
canvas_resize to control the framing
Typical flow when you want to show a result:
canvas_webview_start({ width: 1440, height: 900 });
canvas_screenshot({ fullPage: true });
canvas_webview_stop();
Diagrams (Excalidraw MCP app preset)
canvas_add_diagram — Draw a hand-drawn diagram on the canvas via the hosted
Excalidraw MCP app
- Required:
elements — an array of Excalidraw elements (rectangles, ellipses, diamonds, arrows,
text). Can also be a JSON-array string.
elements must be Excalidraw element objects, not Mermaid/DOT/source-text diagrams. Convert source diagrams to Excalidraw elements first or use a markdown/web-artifact node.
- Optional:
title, x, y, width, height
- The diagram opens inside an
mcp-app node with fullscreen editing and draw-on animations
- CLI equivalent:
pmx-canvas external-app add --kind excalidraw --title "Diagram"
- Edits made in expanded/fullscreen mode are persisted back into the node model context and replayed
when the app iframe remounts.
- Use this when the human needs a quick sketch, architecture diagram, or flowchart and a
geometric
graph node would feel too rigid
- Prefer labeled shapes (
"label": { "text": "..." } on rectangle/ellipse/diamond) over
separate text elements — fewer tokens and auto-centered
- Do not use separate
text elements with containerId/boundElements to place centered text
inside shapes. The hosted SVG preview does not auto-position those; PMX normalizes imported
canonical bound text back into shape labels for hosted app calls.
- For detailed sizing, camera, and label-fit rules, read
references/excalidraw-diagram-authoring.md
before creating dense diagrams.
- Prefer the pastel fill palette in the Excalidraw
read_me (light blue/green/orange/...) for
a consistent look across diagrams
External MCP apps (bring your own)
canvas_open_mcp_app — Open any external MCP Apps
server's ui:// resource as an iframe node on the canvas
- Required:
toolName, transport (http URL or stdio command)
- Optional:
serverName, toolArguments, title, x, y, width, height
- Use when no dedicated preset exists yet. The Excalidraw preset (
canvas_add_diagram) is the
only one today
Web Artifacts
canvas_build_web_artifact — Build a single-file HTML artifact from React/Tailwind source
- Required:
title, appTsx
- Optional:
indexCss, mainTsx, indexHtml, extra files, projectPath, outputPath, deps, includeLogs
- By default it opens the result on the canvas as an embedded app node
- By default it returns compact log summaries; set
includeLogs: true when you need raw stdout/stderr
recharts is available in the scaffold. For additional libraries, pass CLI --deps name,name2
or MCP/API deps: ["name"] before bundling.
- Failed or empty CLI bundles print
ok: false, exit non-zero, and do not create a canvas node.
- Use this when the output should be a richer interactive UI than a simple markdown/file/image node
- Prefer the dedicated
web-artifacts-builder skill when you need the full React + shadcn workflow
- Use the
playwright-cli skill when you need to validate the built artifact in a live browser
HTML Nodes (Sandboxed iframe)
canvas_add_html_node — Add a normal self-contained HTML document rendered in a sandboxed iframe
- Required:
html (full document or fragment; inline <script> and CDN <script src="..."> are allowed)
- Optional:
title, summary, agentSummary, presentation, slideTitles, embeddedNodeIds, embeddedUrls, x, y, width (default 720), height (default 640), strictSize
- Iframe sandbox is
allow-scripts only — no same-origin access, no top-navigation, no forms
- Canvas theme tokens are auto-injected as CSS custom properties (both
--c-* and common --color-* aliases such as --color-text-primary, --color-bg, --color-accent) and updated live when the canvas theme changes
- Use for moderate-complexity visualizations and interactive widgets that need real JS but do not warrant a full React build (Chart.js demos, D3 sketches, custom HTML report views)
- Normal HTML is the default. Only pass
presentation: true when the user explicitly asks for a deck/fullscreen presentation; otherwise do not mark raw HTML as presentable.
- Only presentation-marked HTML nodes expose a browser
Present button. Use it when the HTML is a deck, briefing, or fullscreen review surface; the PMX shell owns the fullscreen overlay and exits via Esc or Exit presentation.
- PMX stores a semantic sidecar (
agentSummary, contentSummary, embedded references) so HTML nodes remain understandable in search, pinned context, and spatial context
canvas_add_html_primitive — Generate a reusable HTML communication primitive as a sandboxed html node
- Required:
kind; run canvas_describe_schema and read htmlPrimitives for the current catalog
- Optional:
title, data, x, y, width, height, strictSize
- Use when markdown would be too dense and a structured visual artifact is clearer: tradeoff grids, implementation plans, PR reviews, module maps, design sheets, explainers, reports, and lightweight human-editable boards/editors
- When the human asks for a PowerPoint-like output, pitch deck, briefing, or presentation, use
kind: "presentation" unless a bespoke raw HTML deck is required. Include slides with short titles, one idea per slide, optional metrics, note fields for speaker notes, and optional theme: "canvas" | "midnight" | "paper" | "aurora" or a custom theme object.
- Read
htmlPrimitives from canvas_describe_schema for the data shape and examples before constructing a payload
- For payload patterns, export loops, and the primitive catalog, read
references/html-primitives.md before creating dense or editable artifacts
Choosing the Right Visual Tier
When the output is more than markdown, pick the lightest tier that fits:
| Tier | Tool | Build cost | When to pick it |
|---|
| Declarative UI | canvas_add_json_render_node / canvas_add_graph_node | None | Schema-driven dashboards, forms, charts; agent-friendly to read back via canvas_get_node |
| Generated HTML primitive | canvas_add_html_primitive | None | Reusable communication artifacts such as choices, plans, reviews, maps, reports, presentations/decks, and lightweight editors |
| Sandboxed HTML+JS | canvas_add_html_node | None | Self-contained HTML with inline JS or CDN scripts; one-off visualizations or report views |
| Hosted MCP app | canvas_open_mcp_app / canvas_add_diagram | None | Interactive editors backed by an external MCP server (e.g. Excalidraw) |
| Bundled React app | canvas_build_web_artifact | Heavy (npm install + bundle) | Multi-component UIs needing React state, routing, shadcn/ui, or Tailwind class composition |
Native Structured UI
Use native json-render and graph nodes when the output should stay fully inside PMX Canvas:
- Use
canvas_add_json_render_node for dashboards, forms, summaries, and interactive UI panels
- Use
canvas_add_graph_node for charts and trend visualizations
- Use the repo-local
json-render-* skills when you need help authoring or refining the spec itself
- Use
canvas_build_web_artifact instead when the result needs a full custom React app rather than a schema-driven UI
MCP Resources
These resources give you read access to canvas intelligence. Read them to understand
what the human has set up and what they're focusing on.
| Resource | What it provides |
|---|
canvas://pinned-context | Content of pinned nodes + nearby unpinned neighbors |
canvas://schema | Running-server create schemas and json-render catalog metadata |
canvas://layout | Full canvas state (viewport, nodes, edges) |
canvas://summary | Compact overview: node counts by type, pinned titles |
canvas://spatial-context | Proximity clusters, reading order, pinned neighborhoods |
canvas://history | Human-readable mutation timeline |
canvas://code-graph | Auto-detected file import dependencies (JS/TS, Python, Go, Rust) |
canvas://skills | Index of bundled agent skills shipped with the install. Each skill is also addressable as canvas://skills/<name> (e.g. canvas://skills/web-artifacts-builder) and returns the full SKILL.md. Read this resource first to discover companion workflows the canvas is built to support. |
Reading Spatial Intent
The canvas://spatial-context resource reveals how the human has organized information:
- Proximity clusters — Nodes placed near each other form implicit groups. If the human
placed three files next to each other, those files are related in their mental model.
- Reading order — Nodes sorted top-left to bottom-right, following natural reading flow.
This implies sequence or priority.
- Pinned neighborhoods — For each pinned node, nearby unpinned nodes are listed. These
are the human's implicit context — things they consider related to what they pinned.
- Annotations — Human-drawn markup is summarized by target/bounds only, e.g. an
annotation over a node or empty canvas region. Use WebView (
canvas_webview_start +
canvas_evaluate/canvas_screenshot) when you need to see whether the mark is an
arrow, line, circle, or other drawn shape. Remove known annotations with
canvas_remove_annotation; otherwise use WebView to identify the mark first.
- Board density matters — On a dense board, spatial context can still read like one large
gallery unless groups and spacing separate the major regions clearly.
Use this spatial intelligence to understand what the human is thinking without them having to
explain it explicitly.
HTTP API Reference
All POST/PATCH endpoints accept Content-Type: application/json. Default base URL: http://localhost:4313
| Method | Endpoint | Purpose |
|---|
| GET | /api/canvas/state | Full canvas state |
| POST | /api/canvas/node | Add node |
| PATCH | /api/canvas/node/<id> | Update node |
| DELETE | /api/canvas/node/<id> | Remove node |
| POST | /api/canvas/edge | Add edge |
| DELETE | /api/canvas/edge | Remove edge ({ "edge_id": "..." }) |
| GET | /api/canvas/snapshots | List snapshots |
| POST | /api/canvas/snapshots | Save snapshot |
| POST | /api/canvas/snapshots/<id> | Restore snapshot |
| DELETE | /api/canvas/snapshots/<id> | Delete snapshot |
| POST | /api/canvas/context-pins | Replace pinned nodes |
| GET | /api/canvas/pinned-context | Get current pins with neighborhood context |
| GET | /api/canvas/search?q=... | Search nodes |
| POST | /api/canvas/json-render | Create a native json-render node |
| POST | /api/canvas/graph | Create a native graph node |
| GET | /api/canvas/schema | Get running-server create schemas, examples, and json-render catalog metadata |
| POST | /api/canvas/schema/validate | Validate a json-render spec or graph payload without creating a node |
| GET | /api/canvas/json-render/view?nodeId=... | View a native json-render or graph node |
| POST | /api/canvas/diagram | Create an Excalidraw external app node |
| POST | /api/canvas/mcp-app/open | Open a tool-backed MCP app node |
| POST | /api/canvas/web-artifact | Build a bundled web artifact and optionally open it on canvas |
| POST | /api/canvas/group | Create group |
| POST | /api/canvas/group/add | Add nodes to group |
| POST | /api/canvas/group/ungroup | Ungroup |
| POST | /api/canvas/arrange | Auto-arrange |
| POST | /api/canvas/focus | Center viewport on node |
| POST | /api/canvas/fit | Fit viewport to canvas bounds or selected nodes |
| POST | /api/canvas/clear | Clear canvas |
| POST | /api/canvas/update | Batch update positions |
| GET | /api/canvas/spatial-context | Spatial clusters and reading order |
| POST | /api/canvas/undo | Undo |
| POST | /api/canvas/redo | Redo |
| GET | /api/canvas/history | Mutation history |
| GET | /api/canvas/code-graph | File dependency graph |
| GET | /api/workbench/events | SSE event stream |
Workflow Patterns
These are operational recipes — how to sequence canvas calls for a few
recurring shapes of work. They are not the project's use cases (those live
in the README and are intentionally non-exhaustive). The patterns here exist
to make the agent's tool-call sequencing concrete: which MCP tool fires
when, what to pin, when to read canvas://pinned-context, when to snapshot.
Responding to Pinned Context
When the human pins nodes, they're telling you what matters. This is the most important
collaboration pattern:
- Read
canvas://pinned-context — get the content of pinned nodes and their neighborhoods
- Read
canvas://spatial-context — understand how the whole canvas is organized
- Optionally read
canvas://summary — see pinned nodes in the context of the full canvas
- Interpret what you find:
- What types are the pinned nodes? (files = code focus, status = progress, markdown = concepts)
- Are they clustered together (single focus) or spread across the canvas (multi-topic)?
- What unpinned nodes are nearby? These are the human's implicit context
- What's the reading order? Top-left to bottom-right suggests sequence or priority
- If an
mcp-app node is pinned, treat it as “important but partially opaque” and use nearby
graph/file/markdown nodes to recover the missing semantic detail
- Respond by summarizing what you see, what you think the human is focusing on, and ask
if they'd like you to act on it (add related nodes, investigate further, etc.)
When to use pinned-context vs spatial-context:
canvas://pinned-context — "what did the human explicitly pin, and what's near those pins?"
canvas://spatial-context — "how is the entire canvas organized spatially?"
- Read both when you need the full picture; read just
pinned-context for quick pin checks.
Investigation Board
When debugging, lay out evidence spatially to see connections:
- Create a root node describing the bug/issue
- Add evidence nodes: logs, stack traces, relevant code files (use
file nodes for source)
- Connect evidence to root with
references edges
- Add a hypothesis node, connect with
flow edge
- As you investigate, add findings and update connections
- Use
status nodes to track what you've checked
- Group evidence nodes together, and investigation tasks together
- Arrange with
flow layout, then fine-tune positions if needed
Bug Report ──references──> Error Logs
│ │
│ references
│ ▼
└──flow──> Hypothesis ──flow──> Fix
│
flow
▼
Verification
Architecture Diagram
Show system components and how they interact:
- Create
markdown nodes for each service/component (include port, tech stack in content)
- Use
flow edges for data flow, depends-on for dependencies — always label edges
- Group related services with
canvas_create_group (e.g., "Application Services", "Data Layer")
- Use colors: green for healthy, yellow for degraded, red for down
- Arrange with
grid layout initially
- For tiered architectures, fine-tune with explicit
x/y via canvas_update_node to show
layers (e.g., gateway at top, services in middle, data stores at bottom)
- Connect pipeline stages with
flow edges where applicable
Task Plan with Dependencies
Track work items and their relationships:
- Create
status nodes for each task
- Color-code: green=done, yellow=in-progress, red=blocked, gray=queued, blue=ready/available
- Connect with
depends-on edges — use dashed style for blocked dependencies, solid for
satisfied ones
- Update status nodes as work progresses using
canvas_update_node
- Arrange with
flow layout to show the dependency chain left-to-right
- Group related tasks if the plan has distinct phases
Code Exploration
Understand a codebase by visualizing file relationships:
- Add
file nodes for key source files (content auto-loads and live-updates)
- The code graph auto-detects imports and creates
depends-on edges automatically — you
don't need to manually add import-based edges. You can still add manual edges for
conceptual relationships beyond imports (e.g., "middleware validates using jwt")
- Read
canvas://code-graph for dependency analysis: central files, isolated files
- Group related files with
canvas_create_group (e.g., "Auth Module", "API Routes")
- Pin important files so the human sees them highlighted
- Arrange with
grid layout after adding files
Interactive Artifact Builds
When the user wants a real browser app instead of static notes:
- Use the
web-artifacts-builder skill if the UI needs React state, routing, or shadcn-style components
- Build with
canvas_build_web_artifact
- Keep
openInCanvas enabled unless the user explicitly wants only the output file
- Use the returned
projectPath as the reusable source workspace for iterations
- Use the returned
path for sharing or for opening the generated artifact outside the canvas
- Use the
playwright-cli skill if you need to verify the artifact route or embedded app behavior in a browser
Status Dashboard
Monitor ongoing processes:
- Create
status nodes for each metric/process
- Use semantic colors: green=passing, yellow=running, red=failing, gray=queued
- Connect sequential pipeline stages with
flow edges (label: "then", "triggers")
- Update nodes in-place as state changes using
canvas_update_node — never delete and recreate,
as that loses position and edges
- Arrange with
grid layout
- The human sees real-time updates via SSE
Before/After Comparison
Show two states side by side for the human to compare:
- Take a snapshot before changes:
canvas_snapshot with name "before-X"
- Make changes to the canvas
- Use
canvas_diff to show what changed
- Or: create two groups ("Before" and "After") with corresponding nodes
Save and Start Fresh
When the human wants to explore a different approach without losing current work:
- First, save the current state:
canvas_snapshot with a descriptive name
- Then clear:
canvas_clear (never clear without snapshotting first)
- Set up the new workspace with initial nodes
- Tell the human the snapshot name and that
canvas_restore can bring everything back
Best Practices
-
Start once, reuse always. Don't restart the canvas for each task. Build on the
existing canvas state.
-
Titles are scannable. Keep titles short (3-6 words). Put details in content.
-
Label every edge. Unlabeled edges lose meaning. "depends on", "calls", "blocks"
are all more useful than a bare arrow.
-
Auto-arrange after batch adds. When adding multiple nodes, call canvas_arrange
once at the end, not after each node.
-
Update in place. Use canvas_update_node to change status, content, or color.
Don't delete and recreate — that loses position and edges.
-
Clean up. Remove nodes that are no longer relevant. A cluttered canvas is worse
than no canvas.
-
Read before writing. Check canvas://layout or canvas_get_layout before adding
nodes to avoid duplicates and understand the current state.
-
Use pinning. When you want the human to focus on specific nodes, pin them.
When the human pins nodes, read canvas://pinned-context to see what they care about.
Prefer one intent-setting markdown pin plus a small set of concrete output pins over pinning a
whole gallery.
-
Snapshot before destructive changes. Before clearing or major reorganization,
save a snapshot so you can restore if needed.
-
Prefer MCP tools over HTTP. When running as an MCP server, use the canvas tools
directly rather than shelling out to curl. The tools handle all the details.
-
Use groups for visual organization. When 3+ nodes are related, wrap them in a
group to make the relationship visible at a glance.
-
Use file nodes for source code. File nodes auto-watch for changes and update
live. This is better than pasting code into markdown nodes.
-
Comparison boards need structure, not just content. For galleries and evaluations, use a
named group, give the area breathing room, and keep related charts/artifacts inside that
region instead of letting them drift into the main cluster.
-
Capture external app IDs immediately. For Excalidraw and other mcp-app nodes, store the
returned node ID or pin the node right away. Search/title rediscovery is less reliable there
than for markdown, graph, or file nodes.
-
Pair app nodes with explainers. If you create or pin a web artifact or Excalidraw node,
add a nearby markdown, graph, or file node that explains what the app is for. This makes
pinned context far more useful to later agents.
Persistence
Canvas state auto-saves to .pmx-canvas/canvas.db on every mutation (debounced 500ms). State
loads automatically on server start. The SQLite DB is git-committable — spatial knowledge
persists across sessions.
Snapshots, context pins, and large node blobs are stored in the same DB. Web artifacts land in
.pmx-canvas/artifacts/. Legacy JSON state, snapshot, and blob files are auto-imported into
SQLite and renamed to .bak on first boot.
Stop the server or flush/close the SDK before committing canvas.db; shutdown checkpoints SQLite
WAL data into the DB file.
Real-Time Collaboration
The canvas supports real-time human-agent collaboration:
- Human pins nodes in browser → agent reads
canvas://pinned-context
- Agent adds/updates nodes → human sees changes instantly via SSE
- Human moves/groups nodes → spatial arrangement communicates intent
- Agent reads spatial context → understands implicit relationships
This bidirectional flow means the canvas is a shared workspace, not just an output display.
Pay attention to what the human is doing on the canvas — their spatial choices are meaningful.