| name | agent-vision-diagrams |
| description | Create and iterate on shareable visual diagrams (architecture maps, flowcharts, state machines, class/domain models, mindmaps, dependency maps) via the Agent Vision MCP server. Use when the user asks for a diagram, visualization, or "draw this out" during a conversation. |
Agent Vision — creating diagrams for the user
Agent Vision is a hosted diagram app. You write a small JSON document (the
spec — the DSL), the app renders it beautifully in either a flat 2D or a
true isometric projection, stores every change as an immutable revision, and
gives you a share URL plus a rendered PNG so you can show the user
immediately.
Connect
Onboarding is two short steps: the user hands you a URL and (usually) a
token; you plug them into your client. After that you drive Agent Vision on
their behalf and share diagrams back to them.
App URL: https://your-app.example.com
MCP endpoint: <app-url>/_agent-native/mcp (Streamable HTTP).
Given only the bare domain, this skill is self-discoverable: fetch
<app-url>/llms.txt (a concise map) → it links to <app-url>/skill.md (this
file, canonical), the MCP endpoint, and the auth flow below.
Machine-readable HTTP action catalog: <app-url>/action-catalog.json.
Step 0: check for pre-provisioned credentials first
Before any auth flow, check the environment — provisioned containers often
carry credentials already:
echo "${AGENT_NATIVE_VISION_URL:-unset} ${AGENT_NATIVE_VISION_TOKEN:+token-present}"
If both are set, skip ALL auth below: normalize the URL once so a trailing slash
doesn't produce //_agent-native/..., then send
Authorization: Bearer $AGENT_NATIVE_VISION_TOKEN on every call.
VISION_ORIGIN="${AGENT_NATIVE_VISION_URL%/}"
curl -sS "$VISION_ORIGIN/_agent-native/actions/list-projects" \
-H "Authorization: Bearer $AGENT_NATIVE_VISION_TOKEN"
Primary: agent-driven device flow — no pre-minted token needed
You (the agent) drive this yourself, given only the app URL. No password or
secret ever passes through you. This flow requires a pause: after step 2
you must WAIT for the user to click Authorize (keep polling calmly — pending
is normal for a minute or more; don't give up, don't invent another route).
1. Start the device flow (no auth required):
curl -sX POST <app-url>/_agent-native/mcp/connect/device/start \
-H "Content-Type: application/json" -d '{}'
2. Give the user the URL. Tell them in chat:
Open this link and click Authorize this device:
<verification_uri_complete>
(code: <user_code> — shown on the page too, no need to type it)
They can open it on any device where they're already logged in — it does
not need to be the machine you're running on.
3. Poll until approved (every interval seconds, up to expires_in):
curl -sX POST <app-url>/_agent-native/mcp/connect/device/poll \
-H "Content-Type: application/json" \
-d "{\"device_code\":\"<device_code>\"}"
4. Wire up the token:
claude mcp add --transport http agent-vision \
<mcpUrl> --header "Authorization: Bearer <token>"
(Raw-HTTP agents: the same bearer works directly against
<url>/_agent-native/actions/<name> — see below.)
Tokens are per-user, scoped, expire (default 365 days), and are revocable any
time from <app-url>/_agent-native/mcp/connect → Existing connections.
Fallback: user-minted token
If you can't poll (no outbound network) or the user prefers to mint ahead of
time: they log in, open <app-url>/_agent-native/mcp/connect, click Create
connection token, and paste the app URL + token to you. Wire it up
exactly as step 4 above.
OAuth-capable clients with a local browser (Claude web/Desktop, Cursor,
ChatGPT — not a headless Claude Code session): skip all of the above — add
just <url>/_agent-native/mcp with no header and approve the connector in the
client's own sign-in popup.
Raw-HTTP agents (no MCP client) — usually the fastest path for headless
coding agents after auth. Every tool is a plain HTTP endpoint at
<url>/_agent-native/actions/<name>, and the same bearer token works here.
Send Authorization: Bearer <token> on every call. Verbs are strict: reads
are GET with query params — list-projects, list-diagrams, get-diagram,
render-diagram (GET .../actions/render-diagram?diagram=<id>&see=true),
list-icons, share-diagram, validate-spec; writes are POST with a JSON
body — create-diagram, update-diagram, delete-diagram. POSTing a read
returns 405 {"error":"Use GET"} (note: curl -f hides that body). Same
schemas, same responses as MCP. Without the header these routes return
401 {"error":"Unauthorized"}.
Prefer MCP when your host already has a native MCP connector/OAuth flow, because
the server can advertise tools and future embedded app affordances there. Prefer
raw HTTP when you are a shell-based agent that can make authenticated requests
directly and wants the fewest setup steps.
For a compact machine-readable map of raw HTTP methods, paths, request shapes,
response fields, and curl examples, fetch <app-url>/action-catalog.json.
First contact (5-line recipe)
1. list-projects → discover/confirm the workspace
2. create-diagram { title, spec } → returns { title, url, svgUrl, pngUrl,
preview, warnings }
(compatible in-app agent surfaces also
receive a reduced inline image)
3. LOOK at the render, read warnings → open/fetch preview.pngUrl or pngUrl
4. update-diagram { diagram, ops:[...] } → fix warnings (repeat 3–4 as needed)
5. render-diagram { diagram, see:true } → take a last look via preview.pngUrl
(or inline image when supported), then share url
The share url (<app-url>/d/<slug>) is public and needs no token — that is
the link you give the user. .svg / .png variants are the raw images.
The loop — render → look → fix → share (always this)
create-diagram { title, spec } → returns title, url, svgUrl,
pngUrl, preview, warnings, and, on compatible in-app agent surfaces, a
reduced inline image you can look at directly.
- Look at the render and read every warning. In any surface, open/fetch
preview.pngUrl or pngUrl. In the in-app agent, the image may also come
back with the call (create defaults to it; on any later call pass
see: true). Raw HTTP/MCP clients should treat see=true as "please include
inline vision if this transport supports it" and still use the preview URLs.
- Fix problems with
update-diagram { diagram, ops: [...] } — small patches,
not a whole-spec resend for one change. Don't ask for an image on every
micro-patch (see defaults to false on update — it's context-costly);
look once after a batch.
- Before you finalize/share, take one deliberate look:
render-diagram { diagram, see: true } (the canonical "let me look" call).
- Share the
url with the user (embed the image if your surface allows). Each
later change is another update-diagram — every write is a new revision; old
links stay valid (?rev=N pins one).
Never share a diagram you haven't rendered and looked at, or one with
outstanding warnings. Warnings are a todo list, not decoration.
see in one line: request a reduced (~768px), cheap inline copy of the
render for agent surfaces that support image tool results — default TRUE on
create-diagram, FALSE on update-diagram, and useful on render-diagram with
see: true. Raw HTTP/MCP callers still get preview, pngUrl, and svgUrl;
open/fetch those URLs to inspect the render. The user-facing pngUrl stays full
quality regardless.
Isometric view is real. The same spec renders isometrically when you set
view.mode: "iso" — groups become tinted ground zones, each node becomes an
extruded tile with its icon billboarded flat on top, edges follow the iso grid,
and every label stays upright and readable. It's a pure display choice over the
identical document (layout, ids, and edges are unchanged), so you can flip it
per-render without rewriting anything: render-diagram { diagram, mode: "iso", see: true } to look at an isometric render, or append ?mode=iso to a share
.svg/.png URL. Set view.mode:"iso" in the spec to make iso the diagram's
default projection. Icon-forward, grouped system maps read best in iso; dense
text-heavy diagrams usually read better flat.
The spec, in one example
{
"version": 2,
"title": "Checkout Flow",
"kind": "system",
"view": { "mode": "2d", "direction": "right", "callouts": "all" },
"groups": [
{ "id": "aws", "label": "AWS", "color": "slate" }
],
"nodes": [
{ "id": "user"
Rules that keep output good:
- Omit x/y and let auto-layout work. Only set positions (integer grid
cells, ~40px each; halves like
1.5 allowed for fine placement) when you need
precise control; then check the render.
- Ids: short kebab-case — you'll reuse them in
ops patches.
- Labels ≤ 3 words where possible. Use
detail for compact subtitles; card
detail wraps into the available width/height, so widen/tall cards (w: 7, h: 2 or h: 3) when the subtitle matters. body works on rect/note
shapes AND on card (it grows into a real card — header, divider, prose
body; auto-sizes taller, no need to set h), or use callout for prose
that should sit beside the node instead of inside it.
- Groups for real boundaries (cloud account, network zone, team, layer,
namespace) — not decoration. Nest sparingly (≤ 3 deep).
- Edges: default arrow flows with
view.direction. kind carries grammar:
inherit (hollow triangle), compose (diamond tail), depend (dashed),
transition (state machines — label = event/guard), assoc (plain line).
cardinality: { from: "1", to: "0..*" } for domain models.
The color / idiom grammar
These come from real diagrams that read well. Follow them and a first render
lands "credible" without fiddling.
Color = category, applied consistently. Pick one token per kind of thing
and never rainbow. The default semantic mapping:
| Token | Means |
|---|
blue | apps / services / compute (the default) |
green | data stores, databases, workers that own state |
purple | external / third-party / serverless functions |
teal | external SaaS, cloud storage |
amber | notes, caution, "in progress" |
red | problems, failure states |
slate | infrastructure, grouping planes |
pink | queues / buses / message rails |
The load-bearing rule: whatever mapping you choose, the same category is
always the same color across the whole diagram (all Lambdas purple, all
services green, all external teal…). Consistency is what makes a busy map
legible.
Idioms — reach for these when a plain box+arrow map undersells the content:
- Category tints — the color grammar above: card fill = what the node is.
- Compact card detail — default
card nodes are summary tiles: icon +
short label + wrapped detail. They are good for "Search, traces, service
maps, dashboards"; they are not paragraph boxes.
- Real cards — add
body to a card node and it grows into a header
(icon + label/detail, anchored to the top) + divider + prose body below, in
the normal sans font — the same --- compartment-divider convention rect
body uses. Reach for this over rect+body when the node is still
conceptually a card (has an icon, a category color) but needs a few lines of
prose under it — e.g. a cache/queue node documenting its eviction policy or
retry behavior inline instead of behind a callout.
- Bus bars — a full-width card (
w: 14+) acting as a rail that many
producers plug into. Mark the plug-in edges trunk: true so they grow
junction dots where they branch. (The Ecosystem "SQS Queue" idiom.)
- Zones — spatial region planes:
group.labelStyle: "zone" +
border: "dashed" (+ optional fill: "transparent") renders a tinted plane
with a big rotated edge label ("Front Office", "Server Room"). Keep the
default "title" for entity/namespace boxes.
- Container icons + title placement —
group.icon (same catalog as
node.icon, see list-icons) draws a small glyph ahead of the label in a
container's title strip — onemodel-style icon+title containers. Only applies
to the default labelStyle: "title" (zone labels ignore it). Three
orthogonal controls place the title:
titleLocation: "top" | "bottom" | "left" | "right" — which edge it hugs
(default top). left/right run it ALONG the vertical edge, rotated to
read bottom-to-top (the icon stays upright) — a spine label.
titlePosition: "inside" | "border" | "outside" — where it sits relative
to that edge (default inside). border straddles the edge and the
border stroke is knocked out behind the label, fieldset-legend style.
titleAlign: "left" | "center" — where it sits ALONG that edge (default
= hug the start). Horizontal on top/bottom, vertical on left/right.
Six worked examples
Each is a complete create-diagram spec. They mirror the app's fixture corpus.
1 · System map (grouped bands, category tints, icons)
{
"title": "Gbase Infrastructure", "kind": "system",
"view": { "direction": "right" },
"groups": [
{ "id": "azure", "label": "Azure Services", "color": "slate" },
{ "id": "storage", "label": "Storage", "color": "slate" },
{ "id": "ext", "label": "External Services", "color":
2 · Flowchart (decision diamonds, labeled branches)
{
"title": "Order Fulfillment", "kind": "flow", "view": { "direction": "down" },
"nodes": [
{ "id": "start", "label": "Start", "shape": "ellipse", "color": "green" },
{ "id": "read", "label": "Read order", "shape": "rect" },
{ "id": "stock", "label":
3 · State machine (transition edges carry the event)
{
"title": "Job State Machine", "kind": "state", "view": { "direction": "right" },
"nodes": [
{ "id": "idle", "label": "Idle", "shape": "rect", "color": "slate" },
{ "id": "running", "label": "Running", "shape": "rect", "color": "blue" },
{ "id": "paused"
4 · Domain model (entity containers + member chips + cardinality)
Containers are the entity boxes; member card nodes are the fields/roles;
cardinality rides the edge between containers. (For a classic UML box use
shape: "rect" + a body with --- compartment dividers instead.)
{
"version": 2, "title": "Media Domain Model", "kind": "class",
"view": { "direction": "down" },
"groups": [
{ "id": "user", "label": "User", "color": "indigo" },
{ "id": "order", "label": "Order", "color": "blue" },
{ "id": "item", "label": "LineItem"
5 · Icon brainstorm (bare icon nodes, no edges)
Pure ideation — big brand glyphs with labels, nothing connected. Let the
pictures carry the thought. size: "l" makes hero icons prominent.
{
"version": 2, "title": "Thinking About Coordination", "kind": "free",
"nodes": [
{ "id": "redis", "label": "Redis locks", "icon": "redis", "shape": "icon", "size": "l" },
{ "id": "pg", "label": "Postgres LISTEN","icon": "postgres", "shape": "icon", "size": "l" },
6 · Dependency map (namespace containers, removed edges, code text)
{
"version": 2, "title": "Dependency Map", "kind": "class",
"view": { "direction": "up" },
"groups": [
{ "id": "video", "label": "MediaProcessing.Video.Combine", "color": "purple", "titleAlign": "left" },
{ "id": "msg", "label": "…Combine.Messaging", "color": "purple", "titleAlign": "left" }
Shapes cheat-sheet
| shape | use |
|---|
card (default) | services, systems, components, entity members — icon + label chip; add body for a real card (header + divider + prose) |
icon | when the picture matters more than the box (iso-friendly, brainstorms) |
rect + body | class/entity boxes; --- lines in body draw compartments |
diamond | decisions in flowcharts |
ellipse | start/end states, actors |
note | sticky annotations, reasoning, open questions |
text | free-floating labels/headings (textStyle:"code" for literals) |
Kinds
kind tunes layout + defaults: system (layered left-to-right), flow
(top-to-bottom flowchart), state (rounded states, transition edges), class
(compartment / entity boxes, assoc edges), mindmap (radial from first node),
ui (wireframe boxes), free (no opinions — good for brainstorms).
Patching with ops
Prefer ops over resending the whole spec. Ops carry every field, v2 included.
{ "diagram": "dg_abc", "message": "add cache layer", "ops": [
{ "op": "set-node", "id": "cache", "label": "Redis", "icon": "redis", "group": "aws", "color": "green" },
{ "op": "set-edge", "from": "api", "to": "cache", "label": "hot reads", "kind": "depend" },
Op set: set-node / remove-node · set-edge (by id, else from+to) /
remove-edge (by id, or by from+to for edges with no id) ·
set-group / remove-group · set-view · set-title · set-meta
(title/description/kind). set-node upserts — a new id creates the node.
Upserts shallow-merge; removes are no-ops when absent. A malformed op returns a
structured { error: "invalid-ops", message, detail:[{index,op,issues}] } body
(never a bare HTTP 400) — read detail and retry.
Icon catalog
node.icon is a flat namespace (111 ids). Call list-icons { query } any
time — it searches by name, tag, or category, e.g. { "query": "Databases" }
returns every database brand icon. Unknown ids render a generic box +
unknown-icon warning, so always verify with list-icons before guessing a
brand name. Every icon has a category: Generic (plain geometric glyphs —
user, server, database, cloud, api, queue, cache, lock, …) or
one of five curated brand groups (top names shown; full list via list-icons):
| Category | Icons |
|---|
| Databases | postgres, mysql, redis, mongodb, sqlite, snowflake, elasticsearch |
| Cloud & Infra | kubernetes, docker, googlecloud, cloudflare, vercel, netlify, terraform, nginx |
| Dev & CI | github, gitlab, githubactions, jira, linear, sentry, grafana |
| SaaS & Tools | stripe, slack, discord, figma, notion, openai, anthropic, shopify |
| Languages & Frameworks | react, nodedotjs, python, typescript, dotnet, nextdotjs, graphql |
Brand icons render at each brand's own color (not the node's color token) —
intentional; a Postgres node still benefits from color: "green" for its
card/chip tint while the glyph stays brand-accurate.
Warnings — treat as your todo list
unknown-icon → call list-icons { query } and pick a real one.
dangling-edge → fix the from/to id.
node-overlap / crowded-region → remove fixed positions or spread them.
label-too-long → shorten; move compact subtitles into detail, or use a
callout/note/rect body for longer prose.
duplicate-id → ids must be unique.
A diagram shared with outstanding warnings is unfinished work.
Good taste defaults
- 5–15 nodes per diagram; split bigger stories into multiple diagrams in one
project.
- One direction of flow; avoid edge crossings by ordering nodes in flow order.
- Title like a slide headline ("Payment processing today"), not a filename.
- "Show me X vs Y" → two groups side by side beats two diagrams.
The canvas (humans co-edit)
Every diagram has an interactive canvas at /diagrams/:id/canvas where the user
drags, connects, styles, and containers by hand. Two things to know:
- Your revisions never clobber their drafts. Unsaved canvas work is a
draft; when your
update-diagram lands, that draft is auto-checkpointed as a
revision first, so nothing is lost — then your write stacks on top. Still,
prefer small ops over whole-spec rewrites, and tell the user in chat before a
big restructure.
- Deep links. Point them at a specific node with
/diagrams/:id/canvas?focus=<node-id> — the canvas opens with that node
selected, its neighborhood in focus, and panned into view.
Deleting a diagram
delete-diagram { diagram, confirm: true } → permanently deletes the diagram
and all its revisions + unsaved draft. Owner-scoped, irreversible, and requires
the literal confirm: true flag. Destructive — always confirm with the user
first before calling it. Returns { deleted: true, diagramId, title, revisionsDeleted }.
Errors (when a call is rejected)
Write actions never return bare HTTP errors for spec problems. A rejected
call returns 200 with { "error": "invalid-spec" | "invalid-ops" | "invalid-args" | "not-found", "message", "errors"? | "detail"?, "warnings"? }
— read errors/detail and retry. Common vocabulary from other tools is coerced
automatically ("LR"→"right", "auto" mode→"2d") with a coerced-value
warning rather than rejected.