Read-only task graph visualizer for Claude Desktop. MCP server (TypeScript/Node.js) wrapping the bd CLI, with a Svelte single-file UI bundle showing interactive DAG visualization, list view, and stats dashboard. Runs inside Claude Desktop as an MCP App (sandboxed iframe).
npm install
npm run build # Build UI bundle + server
npm run dev # Vite dev server (UI only, no MCP host)Add to ~/.config/claude-desktop/config.json:
{
"mcpServers": {
"beads-viz": {
"command": "node",
"args": ["dist/server/index.js"],
"cwd": "/path/to/your/beads-project"
}
}
}The server discovers the Beads project by walking up from cwd to find .beads/config.yaml.
Claude Desktop (MCP Host)
├── Chat conversation
│ "Show me the task graph"
│ → calls visualize-tasks tool
├── MCP Server (Node.js, stdio)
│ ├── visualize-tasks → bd list --all --json → summary + tasks + ui ref
│ ├── poll-tasks (app-only) → bd list --all --json → fresh data
│ ├── show-task (app-only) → bd show <id> --json → task detail
│ └── ui://beads-viz resource → serves dist/index.html
└── Sandboxed iframe (MCP App)
├── Top Strip (project, stats pills, DAG/LIST/STATS tabs)
├── Canvas (DAG / List / Stats views)
├── Bottom Drawer (task detail on click)
└── Polls server every 3s for fresh data
Key insight: The UI is read-only. All task mutations (claim, close, create) happen through the agent chat, not the UI.
src/
├── server/ # MCP server (Node.js target)
│ ├── index.ts # Entry point: McpServer + stdio transport
│ ├── tools.ts # Tool registrations (visualize, poll, show)
│ ├── beads-client.ts # bd CLI wrapper (execFile + JSON parse)
│ └── types.ts # Server-side TypeScript types
└── ui/ # Svelte app (browser target, Vite-bundled)
├── App.svelte # Root: view switching, layout, keyboard
├── main.ts # Svelte mount + MCP bridge init
├── app.css # CSS variables, reset, animations
├── index.html # Vite entry point
├── components/
│ ├── TopStrip.svelte # 32px strip: project, stats, tabs
│ ├── DagView.svelte # ELK.js DAG canvas + SVG edges
│ ├── DagNode.svelte # 156x42 node card with phase colors
│ ├── ListView.svelte # Status-grouped task list
│ ├── StatsView.svelte # Progress ring, phase bars, velocity
│ └── TaskDrawer.svelte # Bottom drawer with task details
└── lib/
├── elk-layout.ts # ELK.js layout computation
├── phase.ts # 9-phase color system (dark + light)
├── stores.ts # Svelte writable stores
├── mcp-bridge.ts # MCP App SDK bridge (connect, poll, theme)
└── types.ts # UI-side TypeScript types
| Command | What it does |
|---|---|
npm run build:ui |
Vite build → dist/index.html (single-file bundle) |
npm run build:server |
tsc → dist/server/*.js |
npm run build |
Both of the above |
npm run dev |
Vite dev server at localhost:5173 (standalone UI testing) |
npm start |
Run MCP server via stdio (node dist/server/index.js) |
The UI is built as a single self-contained HTML file using vite-plugin-singlefile. All CSS, JS, and Svelte compiled output are inlined. The server reads this file at runtime to serve as the ui://beads-viz MCP resource.
- TypeScript MCP Server — MCP Apps SDK is JS/TS-native; Node.js is the natural choice
- Svelte 5 — Small bundle size, reactive stores, matches Hangar's tech stack
- ELK.js — Layered DAG algorithm; deterministic layout unlike D3-force
- Polling (3s) — MCP Apps run in sandboxed iframes; no WebSocket/SSE possible
- bd CLI wrapping — Shell out to
bdvia execFile rather than parsing JSONL directly - Single HTML bundle — Standard MCP App packaging; served as resource
- cwd project discovery — Walk up to find
.beads/config.yaml, same asbditself
9 phases ported from Hangar IDE. Colors map from ELK layer index:
| Phase | Color | Dark Fill | Dark BG |
|---|---|---|---|
| P1 | Cyan | #38bdf8 | #0c2d48 |
| P2 | Indigo | #818cf8 | #1e1b4b |
| P3 | Purple | #c084fc | #2e1065 |
| P4 | Pink | #f472b6 | #4a0d2e |
| P5 | Orange | #fb923c | #431407 |
| P6 | Yellow | #facc15 | #3f3005 |
| P7 | Emerald | #34d399 | #052e16 |
| P8 | Red | #f87171 | #450a0a |
| P9 | Slate | #94a3b8 | #1e293b |
Layer-to-phase mapping: phase = (layer % 9) + 1 (wraps around for deep graphs).
- User asks "Show me the task graph"
- Claude calls
visualize-taskstool - Server runs
bd list --all --json, returns summary + tasks +_meta.ui.resourceUri - Host opens
ui://beads-vizin sandboxed iframe - UI receives initial data via
ontoolresultcallback - UI starts polling
poll-tasksevery 3s viaapp.callServerTool() - Host theme changes propagate via
onhostcontextchanged
This project uses bd (beads) for issue tracking.
bd ready # Find available work
bd show <id> # View issue details
bd close <id> # Complete work
bd graph --all # View full dependency graph
bd sync # Sync with git- Standalone mode: The UI gracefully handles missing MCP host (for
npm run devtesting). The bridge logs a warning and the UI shows empty state. - Theme: Dark theme is default. Light theme adapts via
[data-theme="light"]CSS andphaseColorMapLight. - Bundle size: ~557KB gzipped. ELK.js is ~180KB of that. Could lazy-load for optimization.
- Node viewport: 156x42px compact nodes designed for ~600x500px MCP App viewport.
- No mutations from UI: No Claim/Close buttons. The agent handles all task mutations via chat.
When ending a work session, you MUST:
- File issues for remaining work —
bd create "title" --description "..." - Run quality gates —
npm run buildmust pass - Update issue status —
bd close <id>for completed work - Push to remote:
git pull --rebase bd sync git push
- Verify —
git statusshows clean,git logshows pushed