USER MANUAL · AI
Connect an AI (MCP)
C4Stage is an MCP server — c4stage, version 1.1.0, at https://c4stage.com/api/mcp over streamable HTTP. Claude Code, Cursor or any other MCP client connects with a personal access token and gets seven tools and one prompt to list, read, create and change your canvases. Your own AI runs on your machine and reads the code there. C4Stage receives only the drawing: nodes, edges, flows, health URLs and tags. It never connects to a repository and never receives your source code.
- 1. Make a token. Account chip → AI access → Create token. It is shown once and stored hashed; revoke it there any time. A token acts as you, so the same owner, editor and viewer rules apply, and tool calls are budgeted per account (60 a minute, 3,000 a day). The calls that change a canvas — create_canvas, apply_changes, set_flows and set_document — also spend one of the plan's monthly AI actions each, from the same allowance the assistant draws on.
- 2. Connect the client. Run this in the project you want drawn:
claude mcp add --transport http c4stage https://c4stage.com/api/mcp --header "Authorization: Bearer <token>"
- 3. Run the starter prompt. The server ships c4stage-intake, the starter prompt that tells your AI what to look for: it reads the codebase and infrastructure on your machine, writes a code-grounded brief (C4 levels, edges, flows, health endpoints, tags, zones) and calls create_canvas with it. Follow-ups go through apply_changes, which edits by id and never re-sends the whole canvas, and through set_flows, which writes a canvas's flows as a Mermaid sequenceDiagram.
Watch it draw. Keep the canvas open while the AI works: it joins as a collaborator, shown as a spark in the presence strip instead of a dot. Its cursor travels to each node as it lands, edges and flows follow, and the result is saved as a labeled version in history. With nobody watching, it saves the same way.
Tools
- list_canvases — no arguments. Returns one entry per canvas: id, name, version, node count, flow names, a one-line summary (“8 nodes · 2 flows”, or the document's own), your role (owner, editor or viewer — plus the owner's email, when it isn't you) and its url. A canvas is either a C4 model, whose flows also draw as sequences, or a document, which carries its kind.
- get_canvas — id (required): the canvas id from list_canvases. Returns the full model: nodes with parent nesting, tags and health URLs (icons omitted), edges, flows and tags — plus flows_text, every flow as Mermaid sequenceDiagram blocks (the form set_flows takes back), and sequence_urls, one link per flow into the sequence view. A document returns its envelope and text instead.
- create_canvas — name (required, 1 to 80 characters); intake (optional): an architecture brief (see the starter prompt below), drawn in one call, omit it for an empty canvas; kind (optional): sequence for a C4 canvas authored as a sequence diagram, or a document kind (gantt, timeline, pyramid, quadrant); text (optional, with kind): the sequenceDiagram blocks whose messages become flows, or the document's own dialect; template (optional, with kind: “sequence”): a starter to build the flow on — web-app, microservices, data-platform or blank; actor (optional, up to 40 characters): the presence name shown while it draws, defaults to “Claude”. Returns the new canvas's id and url — a sequence canvas's url opens on the sequence view, on the first flow it made — with a summary, the flow ids and any warnings.
- apply_changes — id (required); changes (required): the changes object, below; label (optional, up to 80 characters): the version label shown in history; actor (optional, up to 40 characters): presence name, defaults to “Claude”. Returns the same summary as create_canvas, plus who was watching live.
- set_flows — id (required); text (required): one or more Mermaid sequenceDiagram blocks; label (optional, up to 80 characters): the version label, defaults to “MCP · set_flows”; actor (optional). Upserts every flow in the text by name — a flow you re-emit replaces its stops, a new title adds a flow, and a flow you leave out is untouched. A message resolves to a connection between those participants, creating the connection, and as a last resort a missing participant, when the model has none. Returns the flow ids, the ids of anything it created, warnings, and the sequence urls. On a document it is refused, pointing at set_document.
- set_document — id (required); text (required): the whole document in its text form — a Mermaid gantt, timeline or quadrantChart block, or a C4Stage pyramid block; label and actor (optional). Elements are matched by id, so an id kept in the text keeps its comments and walkthrough stops. On a C4 model it is refused, pointing at apply_changes.
- get_health — id (required): the canvas id. Returns per-node status — healthy, down or unknown (no health URL) — with HTTP status and latency where probed.
Flows as sequences
A canvas's flows read and write as Mermaid sequenceDiagram text: get_canvas returns them as flows_text, and set_flows takes that form back. A title line names the flow (the upsert key), participant lines fix the column order of its sequence view, a message is a step, a self-message or Note over is an operation inside that participant (Note left of and Note right of pin its panel to that side), a par / and / end group is one parallel step, a loop or opt … end block is a fragment around the stops inside it (nesting kept), and a %% description: line under the title carries the flow's description.
set_flows · id: "shop" · label: "Checkout with 3-D Secure" text: sequenceDiagram title Checkout participant customer as Customer participant web as Storefront participant payments as Payments customer->>web: Submits the basket web->>payments: Requests authorization payments->>payments: Runs 3-D Secure payments->>web: Returns the result
The changes object
apply_changes takes a changes object — any combination of these keys, all optional: name, upsertNodes, removeNodes, upsertEdges, removeEdges, upsertFlows, removeFlows, upsertTags and removeTags. Node upserts merge field by field onto the existing node — send null for a field to clear it. Edges, flows and tags upsert whole: sending one replaces everything stored under that id. A flow's participants is the column order of its sequence view; to author the stops themselves as a sequence diagram, use set_flows. Removing a node cascades — its descendants go with it, then every edge touching any of them, then those edges' steps in every flow; a flow left with no steps is dropped entirely.
{
"upsertNodes": [{ "id": "payments-api", "description": null, "owner": "Payments" }],
"removeNodes": ["legacy-worker"],
"upsertTags": [{ "id": "tier-1", "name": "Tier 1", "color": "#e05c5c" }]
}Limits and errors
- A missing or revoked token gets a 401.
- Tool calls are budgeted per account — 60 a minute, 3,000 a day (MCP_RPM / MCP_RPD to change it); a call over budget comes back as a tool error naming the wait. create_canvas, apply_changes, set_flows and set_document also spend the plan's monthly AI actions; past the allowance the tool error names the plan and the upgrade path.
- apply_changes on a canvas you can only view is refused — you need editor access.
- A change that fails validation comes back as an error naming the field and what's wrong with it; nothing is saved. A sequenceDiagram or document text that does not parse comes back as an error naming the line.
Working without MCP? Paste the same c4stage-intake prompt into any AI that can read your code where it runs, then hand the JSON it returns to the assistant, or to us.
The starter prompt (c4stage-intake), in full
You are producing an **architecture intake** for a project so it can be drawn as a living C4 canvas in C4Stage. C4Stage renders C4 levels as nested cards on one canvas (actors, systems, containers, components, datastores), draws directed edges between them, plays named **flows** step by step over the diagram, polls a **health URL** per runtime and rolls status up through every level, and filters by **tags**. Your job is to read this codebase and its infrastructure and hand back everything the canvas needs, grounded in files, not in guesses.
If the C4Stage MCP server is connected, call its `create_canvas` tool with the finished intake as the `intake` argument instead of printing JSON; use `apply_changes` for later corrections. Otherwise reply with the JSON block described below.
## Ground rules
1. **Code-grounded only.** Every node, edge, endpoint and flow must come from something you can point at: route files, controllers, clients/SDK calls, queue producers and consumers, ORM models, docker-compose, Kubernetes manifests, Terraform/IaC, CI/CD config, OpenAPI specs, env files, READMEs. Put the file references in the `evidence` arrays (`path:line` or `path`).
2. **Never invent an endpoint or a hostname.** If a health endpoint does not exist, say so and propose one (see Health below). If a hostname is unknown, write `"UNKNOWN"` and add an open question.
3. **Redact secrets.** No tokens, passwords, connection strings with credentials, or private IPs. Hostnames without credentials are fine.
4. **Stable ids.** Every `id` is a kebab-case slug that will be reused forever (`payments-api`, `orders-db`). Edge ids are `<source>--<target>--<short-verb>` (`web--payments-api--charge`).
5. **Write for a newcomer.** Descriptions are 1 to 2 sentences, at most 220 characters, and say what the thing is *for* and what would break without it. No implementation trivia unless it explains a flow.
6. **Say what you don't know.** Anything inferred rather than read goes into `assumptions`; anything you could not resolve goes into `openQuestions`. Do not pad either list with generic questions.
7. **Size.** First canvas: 25 to 60 nodes. Prefer fewer, well-described nodes over an exhaustive dump. Go to component level (L3) only inside the 2 to 4 containers where it explains a flow.
## What to cover
**Level 1 — Context.** Every kind of person or role that touches the system (`actor`), each system you own (`system`), and every external system it depends on with `external: true` (identity provider, payment processor, email/SMS provider, third-party APIs, partner systems, managed SaaS). Managed data services that hold your data (RDS, Redis, S3, a warehouse) are `datastore` nodes, not systems.
**Level 2 — Containers.** Inside each owned system, one node per separately deployable or runnable thing: web apps and SPAs (shape `browser`), APIs and services (`card`), workers, schedulers and cron jobs (`card`), queues, topics, streams and pipelines (`pill`), databases, caches, object stores and search indexes (`kind: datastore`, shape `cylinder`), and communication transports such as pub/sub channels, SSE/WebSocket gateways, firewalls and VPN edges (`tube`). Set `parent` to the owning system's id. Give each its `technology` (language/framework/runtime and the major version if visible).
**Level 3 — Components.** Only for the containers that carry the important flows: the modules, routers, handlers, services or adapters a reader must see to follow the story (`kind: component`, `parent` = the container id). Skip utility code.
**Edges.** One edge per real dependency, direction = who initiates the call or publishes the message. `label` is a short verb phrase from the caller's point of view ("charges card", "publishes order.created", "reads sessions"). `tech` names the protocol or channel ("HTTPS/REST", "gRPC", "GraphQL", "SQL", "Redis", "Kafka topic orders", "SQS queue x", "SSE", "WebSocket", "SMTP", "webhook"). Include async edges (producer → queue, queue → consumer) as separate edges through the queue node. Every non-actor node needs at least one edge.
**Flows.** 4 to 8 named flows, each 5 to 15 steps, that together explain how the system actually works:
- the sign-up / sign-in path,
- the core transaction or job the product exists for (the one you would demo),
- one asynchronous or background path (queue consumer, scheduled job, webhook intake),
- one failure or retry path (what happens when a dependency is down, times out, or rejects),
- anything else a new engineer is told about in their first week.
A flow is an ordered list of steps. A step is either an **edge step** (one edge id, or several edge ids when calls happen in parallel at the same moment) with a one-line description of what travels and why, or an **op step**, an operation that happens *inside* one node between edge steps ("validate JWT", "compute totals", "write outbox row"). Use ops sparingly and only where they carry meaning. Name the trigger for each flow (which actor or event starts it).
**Health.** For every runtime container (anything that serves or consumes, not datastores or external systems unless they expose a public status URL):
- If a health/readiness/liveness endpoint exists, give its full URL per environment, the HTTP method, whether it needs auth or a VPN, what response means healthy, and the file that defines it.
- If none exists, set `"status": "proposed"` and describe the minimal endpoint to add: an unauthenticated `GET`, returning `200` quickly, no side effects, optionally checking its own DB/queue connection.
- Reachability matters: C4Stage polls from **its own server** with a plain `GET`, no headers; `2xx` = healthy, anything else or a timeout = down. Flag endpoints that are only reachable from inside a VPC or behind SSO, and suggest the public or VPN-reachable alternative if one exists. Vendor status pages are links, not probes — do not offer them as health URLs.
**Logs (optional).** If the project ships logs to Elasticsearch/OpenSearch/Kibana, give per node the index pattern, a query that isolates that node's logs (KQL or Lucene), and the Kibana discover URL. Otherwise omit `logs`.
**Tags.** 4 to 8 orthogonal facets useful for filtering the canvas: surface (Web, API, Worker), exposure (Public, Internal, Gated), criticality (Tier 1), team or domain (Payments, Identity), environment-specific quirks (Legacy, Deprecated). Each tag gets a hex color and one line of meaning. Assign tags to nodes in `nodes[].tags` by tag **name**.
**Zones.** Groupings that explain ownership or boundaries: teams, environments, network segments, compliance scopes. `variant` is `"zone"` for ownership/environment tint and `"restricted"` for access boundaries (VPN only, allow-listed IPs, corporate network, PCI scope). List member node ids and say why the boundary exists. (C4Stage turns each zone into a filterable tag on its members.)
**Ownership and links.** Per node when known: owning team or person (`owner`), repository URL (`repo`), runbook/docs URL (`docs`).
## Output format
Reply in exactly this order:
1. **Summary** — 5 to 10 lines of prose: what the product does, the main systems, the primary environments, and how confident you are in the intake.
2. **The intake** — one fenced ```json block, valid JSON, following the schema below exactly. Use `null` for unknown optional fields, never empty strings.
3. **Open questions and assumptions** — as bullet lists, only the real ones.
```json
{
"project": {
"name": "Project name",
"oneLiner": "What it does, for whom.",
"primaryRepo": "https://... or null"
},
"nodes": [
{
"id": "kebab-id",
"kind": "actor | system | container | component | datastore",
"name": "Human name",
"description": "1 to 2 sentences, at most 220 characters.",
"parent": "parent-id or null",
"external": false,
"shape": "card | cylinder | pill | browser | tube | null",
"technology": "Next.js 15 / Node 22 (or null)",
"tags": ["Web", "Public"],
"owner": "Team or person, or null",
"repo": "https://... or null",
"docs": "https://... or null",
"health": {
"status": "found | proposed | none",
"urls": { "prod": "https://api.example.com/healthz", "staging": "https://..." },
"method": "GET",
"auth": "none | header | cookie | vpn",
"reachableFromInternet": true,
"healthyWhen": "HTTP 200 with {\"status\":\"ok\"}",
"proposal": "Only when status is proposed: what to add and where.",
"evidence": ["src/routes/health.ts:12"]
},
"logs": { "esIndex": "logs-payments-*", "query": "service:\"payments-api\"", "kibanaUrl": "https://..." },
"evidence": ["path:line"]
}
],
"edges": [
{
"id": "source-id--target-id--verb",
"source": "source-id",
"target": "target-id",
"label": "verb phrase from the caller's view",
"tech": "HTTPS/REST | gRPC | SQL | Kafka topic x | ...",
"evidence": ["path:line"]
}
],
"flows": [
{
"id": "flow-id",
"name": "Flow name",
"description": "What this flow achieves, in one sentence.",
"trigger": "Who or what starts it.",
"steps": [
{ "type": "edge", "edges": ["edge-id"], "description": "What travels and why." },
{ "type": "op", "node": "node-id", "label": "What happens inside the node." },
{ "type": "edge", "edges": ["edge-id-a", "edge-id-b"], "description": "Two calls in parallel." }
]
}
],
"tags": [
{ "id": "web", "name": "Web", "color": "#5B8DEF", "meaning": "Browser-facing surface." }
],
"zones": [
{ "id": "zone-id", "label": "Payments team", "variant": "zone | restricted", "members": ["node-id"], "why": "Why this boundary exists." }
],
"assumptions": ["..."],
"openQuestions": ["..."]
}
```
## Before you answer, check
- Every `parent`, `source`, `target`, flow `edges[]` entry and zone member refers to an id that exists in `nodes` or `edges`.
- Every non-actor node has at least one edge; every edge appears in at least one flow or is clearly a standing dependency.
- No description exceeds 220 characters; no id has spaces or capitals.
- Every health entry has `status`, and every `found` entry has at least one URL and a file reference.
- The JSON parses.
If the repository is too large to cover in one pass, cover Level 1 and Level 2 completely first, then the flows, then go deeper only where the flows need it, and say in the summary what you left out.