USER MANUAL · TOGETHER

Connect an AI (MCP)

C4Stage is an MCP server. Any MCP host — Claude Code, Cursor, an agent of your own — connects with a personal access token and gets tools to list, read, create and change your canvases. Point it at the repository you want drawn, and it draws.

  • 1. Make a token. Account chip → AI accessCreate token. It is shown once and stored hashed; revoke it there any time. A token acts as you — the same owner, editor and viewer rules apply — and tool calls are budgeted per account (60 a minute, 3,000 a day). The two calls that change a canvas, create_canvas and apply_changes, also spend one of the plan's monthly AI actions each — the same allowance as assistant messages.
  • 2. Connect the client. In the project you want mapped:
    claude mcp add --transport http c4stage https://c4stage.com/api/mcp --header "Authorization: Bearer <token>"
  • 3. Ask for the intake. The server ships a prompt, c4stage-intake: the AI reads the codebase and infrastructure, 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.

Watch it draw. Keep the canvas open while the AI works: it joins the canvas as a collaborator — 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 labelled version in history. With nobody watching it simply saves.

Tools

  • list_canvases — no arguments. Returns one entry per canvas: id, name, version, node count, flow names, your role (owner, editor or viewer — plus the owner, when it isn't you) and its url.
  • get_canvasid (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.
  • create_canvasname (required, 1 to 80 characters); intake (optional): an architecture brief — see the intake prompt below — drawn in one call, omit it for an empty canvas; actor (optional, up to 40 characters): the presence name shown while it draws, defaults to “Claude”. Returns the new canvas's id and url, and a summary (name, version, node and edge counts, flow and tag names).
  • apply_changesid (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.
  • get_healthid (required): the canvas id. Returns per-node status — healthy, down or unknown (no health URL) — with HTTP status and latency where probed.

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. 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 and apply_changes 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.

Working without MCP? Paste the same intake instructions into any AI that can read your repository and hand the JSON it returns to the assistant, or to us.

The intake prompt, 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 journeys, 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 journey 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.