Skip to main content
Glama

canvas_present

Read-only

Create or update an artifact on the chat canvas, the side panel that renders rich content next to the conversation. Use this whenever the user asks for a diagram, architecture map, flowchart, sequence diagram, chart, dashboard, widget, visualisation, report, or any self-contained HTML, SVG, JavaScript, or Markdown document they should see rendered, on Canvas. Canvas is the only delivery surface for requested visuals: do not return inline Mermaid/chart blocks, ASCII diagrams, source dumps, or file attachments instead. Use native JSON Render components where possible; for custom HTML/SVG draw actual static elements, never embed Mermaid source or depend on a JavaScript renderer. For HTML/SVG, content must be fully self-contained: inline CSS; no external scripts, stylesheets, fonts, or network calls. HTML renders inside a sandboxed iframe. In the Classic portal, the sandbox exposes window.ankra for live platform data: await window.ankra.getClusterHealth(clusterId) resolves to the read-only health snapshot {cluster_id, health_status, nodes: {total, ready, not_ready}, pods: {total, running, pending, failed, succeeded, crash_loop, high_restart}, problematic_pods: [{name, namespace, issue, restarts}], generated_at}; poll it on an interval of 10 seconds or more for a self-refreshing widget, and render a friendly error state when the call rejects. Omit artifact_id to create a new artifact; the tool result returns the assigned artifact_id. Call again with that artifact_id to update the artifact in place (an open canvas refreshes live) instead of creating a duplicate. Always send the complete artifact content, never a partial diff; to change a json dashboard already on the canvas use canvas_patch instead of resending it. Keep one artifact per deliverable and update it as you iterate. Canvas visual style rule: use Ankra Next (Ink) styling by default unless the user explicitly requests another style. Use quiet neutral surfaces, thin neutral borders, restrained 8px corners, and Inter/system-ui sans-serif text (14px body, readable line-height, modest sentence-case headings); no decorative gradients, glowing borders, emoji headings, saturated blue panels, or nested cards. Use spacing from 4, 8, 16, 32px, one surface per section, and colour only for meaningful warnings/errors or necessary chart distinctions; healthy states stay neutral and must reflect observed data. For dark mode use canvas #0a0a0b, surface #141416, subtle #1c1c1f, text #f4f4f5, muted text #a1a1aa, borders #27272a; for light mode use canvas #fafafa, surface #ffffff, subtle #f4f4f5, text #111113, muted text #5f5f66, borders #e4e4e7. Support both light and dark modes, respecting the portal account theme rather than assuming the operating-system preference. Ankra Next supplies the resolved theme as root data-theme=light or dark, color-scheme, and the active --ink-bg-canvas, --ink-bg-surface, --ink-bg-subtle, --ink-fg-default, --ink-fg-muted, --ink-border-default palette. Use these variables with light-dark(light, dark) fallbacks, or root [data-theme] selectors for other colours. For standalone/downloaded documents default color-scheme to light dark using a low-specificity :where(:root) rule; do not override the portal-selected scheme or theme. Do not rely solely on prefers-color-scheme media queries in embedded previews. Inline the styling; portal stylesheets and fonts are not inherited. Make HTML responsive from a narrow canvas to the full workspace: width:100%, min-width:0, fluid grids that stack on small widths, wrapping text, and viewBox-based SVG diagrams instead of fixed desktop widths. For HTML/SVG present the primary content as static HTML/CSS or SVG: Ankra Next blocks scripts and does not expose window.ankra, so its canvas must not depend on JavaScript or that bridge. Do not claim live updates for a static snapshot; label the observation time when available. Prefer language="json" (a JSON Render spec) for dashboards, charts and diagrams in Ankra Next: native Ink components that follow the account theme and resize; never JSX, HTML, CSS, scripts, Mermaid or markdown fences inside it. content is a JSON-encoded string: {"schema_version":2,"root":"page","elements":{"page":{"type":"Grid","props":{"layout":{...}},"children":["cpu"]},"cpu":{"type":"LineChart","props":{"label":"CPU","binding":{...}}}},"time":{"default":"24h","options":["1h","6h","24h","7d","30d"],"compare":false},"filters":[{"name":"cluster","label":"Cluster","default":""}]}. Every element is {type, props, children?}; one connected tree, ids unique, a letter then letters, digits, - or _ (max 64), no cycles or shared children, at most 128 elements, 12 levels and 32 children per element. time options come from 1h|6h|24h|7d|30d and include default; filters (max 3, each once) are cluster|environment|application. time, filters, binding, layout, annotations and sparkline need schema_version 2; Gauge, Donut and Diagram kind/group work in v1 too. Components (props strict; label max 120, text max 2000, unit max 20; only containers take children): Stack {}; Grid {layout?}; Section {title, description?}; StatGroup {} (1-4 Stat children); Text {text, muted?}; Note {title?, text, tone?:"neutral"|"warn"}; Badge {text, tone?:"neutral"|"warn"|"danger"}; Stat {label, value:string, unit?, delta?, tone?} or {label, binding, unit?, sparkline?:boolean} (sparkline only when bound; it needs a timeseries); Gauge {label, value:number|null (required; null is unknown), unit?, min, max (> min), thresholds?:{warn?, danger?, direction?:"above"|"below"}} or {label, binding, unit?, min, max, thresholds?} (binds a value or timeseries dataset; colour only at or beyond a level); Donut {label, unit?, segments:[{name (unique), value:number>=0|null}] (1-8)} or {label, binding, unit?, label_key?, value_key?} (binds a table dataset; from nine rows it shows the top 7 plus Other); LineChart {label, labels:string[] (2-100), series:[{name, values:(number|null)[]}] (1-4, each matching labels), unit?} or {label, binding, unit?, annotations?:boolean} (annotations marks the cluster's deployments and alerts); BarChart {label, bars:[{label, value:number|null}] (1-24), unit?} or {label, binding, unit?, label_key?, value_key?}; BarList {label, items:[{label, value:number|null, detail?}] (1-20), unit?} or {label, binding, unit?, label_key?, value_key?, limit?:1-20}; Histogram {label, unit?, series:[{key, label, tone?:"neutral"|"warn"|"danger"}] (1-4), buckets:[{start, end, values:{:number|null}}] (1-120, unix seconds; null is not collected), overlay?:{label, unit?, points}, gaps?:[{start, end}]} or {label, binding, unit?} (stacked bars per time bucket; binds application.requests_by_status, application.log_volume or cluster.pod_restarts_by_bucket); Table {label, columns:string[] (1-8), rows:string[][] (max 100, each matching columns)} or {label, binding, unit?}; StatusList {label, items:[{label, state:"ok"|"warn"|"danger"|"unknown", detail?}] (1-50)} or {label, binding, unit?}; Diagram {label, nodes:[{id, label (max 32), detail? (max 64), column 0-3, row 0-7, tone?, kind?:"users"|"ingress"|"service"|"database"|"cache"|"queue"|"storage"|"cluster"|"external", group? (max 32; a labelled boundary around nodes sharing it)}] (1-24, distinct cells), edges:[{from, to, label? (max 40)}] (max 48, unique, between distinct nodes)}. Grid layout: {"columns":12,"items":{"":{"x":0,"y":0,"w":6,"h":4}}}, integers with x 0-11, w 1-12, x+w<=12, h 1-12 (rows of 32px or more), y 0-384; keys are that Grid's own children, no overlaps, max 32 items; an unplaced child renders full width below. binding: {"dataset":"","params":{"cluster":{"$filter":"cluster"}}}; a param is a literal or {"$filter": a declared filter}, and a cluster param is a cluster id. Never put a time range, query or URL in params: the canvas time control sets the window. A bound panel shows its source and observation time and its own loading, empty, stale, unavailable or denied state, never zero or healthy. A live public link does not redraw a bound Gauge, Donut, Histogram or Stat sparkline: those stay as read. No other props, state, actions, events, visibility, repeat, URLs or components. Dashboards: call list_canvas_datasets first and bind every panel a dataset covers instead of inlining numbers you observed; a binding must use a component in that dataset's fits or it is refused. For a single number bind Stat or Gauge to a value dataset (cluster.cpu_utilisation_percent and cluster.memory_utilisation_percent in %, cluster.cpu_cores_in_use, cluster.memory_in_use, cluster.pod_restarts_in_range, deployments.count), never to a table; a Gauge is fit on a "%" dataset, on one with has_capacity, or when it sets max in the dataset's unit, and its unit, if set, is the dataset's; fleet.availability feeds a StatusList; inline data only for facts no dataset has, never invented, healthy stays neutral. Use one Grid with an explicit layout and one panel per question: LineChart for trends (annotations: true for a cluster), Gauge for a single utilisation-like value, Donut for part-to-whole, BarList or BarChart to rank or compare, StatusList for states, Table only for genuine row detail, never a table standing in for a chart. Text is a caption of at most about 140 characters, never an explanatory paragraph; at most one Note, for a genuine caveat. Never write "n/a", "unknown" or placeholder stats: when a dataset is unavailable, omit the panel or bind it anyway so the panel shows that honest state. For visuals beyond the catalog use static HTML/SVG. Code and Download preserve the spec.

Input Schema

NameRequiredDescriptionDefault
titleYesShort human-readable artifact title shown on the canvas header and the transcript card.
contentYesComplete artifact content. For html send a full self-contained document.
languageYesContent type. html, svg and javascript render as a live preview; the rest display as documents.
artifact_idNoOmit to create a new artifact. Pass the artifact_id from a previous canvas_present result to update that artifact in place.

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A3.8/5.0
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description describes a state-mutating operation: it creates a new artifact, returns an assigned artifact_id, and updates artifacts in place with live refresh in an open canvas. The annotations declare readOnlyHint=true, which asserts the tool does not modify its environment. Despite an enormous amount of genuine behavioral disclosure (sandboxing, no external network, self-contained content, theme handling), the core read/write nature directly conflicts with the annotation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded correctly (purpose, then when-to-use, then constraints, then spec), but it runs to several thousand words and embeds an entire component catalog, Grid layout rules, colour tokens and binding schema inline. Reference material of that volume belongs in docs; here it buries the operative instructions an agent needs at call time.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex create/update tool with no output schema, the description still tells the agent that the tool result returns the assigned artifact_id, that the canvas refreshes live on update, and how content is validated per language. Nothing needed to invoke it correctly appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline is 3, but the description adds real semantics beyond the schema: omit artifact_id to create versus pass it to update in place, always send complete content rather than a diff, and content is described as a JSON-encoded string with a full JSON Render element grammar. Only title/language receive no added nuance beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence gives a specific verb and resource ('Create or update an artifact on the chat canvas') and immediately defines what the canvas is, so an agent can distinguish it from canvas_patch and from chat-only replies. It goes further by enumerating the artifact classes it accepts, leaving no ambiguity about scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states an explicit trigger ('whenever the user asks for a diagram, architecture map, flowchart...'), explicit exclusions ('do not return inline Mermaid/chart blocks, ASCII diagrams, source dumps, or file attachments'), and names the alternative tool for the update case ('to change a json dashboard already on the canvas use canvas_patch instead of resending it'). This is the strongest form of routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources