Read-only task graph visualizer for Beads projects. Runs as an MCP App inside Claude Desktop or VS Code Copilot, showing an interactive DAG, list view, and stats dashboard.
Claude Desktop / VS Code Copilot
├── Chat: "Show me the task graph"
│ → calls visualize-tasks tool
├── MCP Server (Node.js, stdio)
│ ├── visualize-tasks → bd list --all --json
│ ├── poll-tasks → fresh data every 3s
│ ├── show-task → task detail by ID
│ └── ui://beads-viz → serves the UI bundle
└── Sandboxed iframe (MCP App)
├── DAG view (ELK.js layered layout)
├── List view (status-grouped)
├── Stats dashboard (progress, velocity)
└── Task detail drawer
The UI is read-only — all task mutations (create, claim, close) happen through agent chat.
bd) installed and on PATH — install instructions.beads/config.yaml)Verify your setup:
node --version # v18.0.0+
bd --version # any version
bd list --json # should output JSON (run from a Beads project)
The fastest way — no cloning, no building. Just configure your MCP host to use npx:
{
"mcpServers": {
"beads-viz": {
"command": "npx",
"args": ["-y", "beads-viz"],
"cwd": "/path/to/your/beads-project"
}
}
}
npx downloads and caches the package on first run. The -y flag skips the install confirmation prompt.
Where does this go? See the Installation section below for the config file location for your setup (Claude Desktop, VS Code Copilot, WSL, etc.)
If you prefer a permanent install over npx:
npm install -g beads-viz
Then use beads-viz as the command directly:
{
"mcpServers": {
"beads-viz": {
"command": "beads-viz",
"cwd": "/path/to/your/beads-project"
}
}
}
If you prefer to build locally:
git clone https://github.com/pyros-projects/beads-viz.git
cd beads-viz
npm install
npm run build
This produces:
| Output | Description |
|---|---|
dist/index.html |
Self-contained UI bundle (Svelte + ELK.js, single file) |
dist/server/index.js |
MCP server entry point (Node.js, stdio transport) |
Build commands:
| Command | What it does |
|---|---|
npm run build |
Build everything (UI + server) |
npm run build:ui |
Vite build — produces dist/index.html |
npm run build:server |
TypeScript compile — produces dist/server/*.js |
npm run dev |
Vite dev server at localhost:5173 (standalone UI testing) |
The MCP server runs via stdio — the host application (Claude Desktop or VS Code Copilot) starts it as a child process. Configuration depends on where the server runs relative to the host.
Key concept: The
cwdfield determines which Beads project to visualize. The server walks up fromcwdto find.beads/config.yaml.
All examples below show both the npx approach (recommended) and the local build approach. Use whichever you prefer.
The simplest setup. Both Claude Desktop and the server run on the same machine.
Edit ~/.config/claude-desktop/config.json (Linux) or ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
Using npx (recommended):
{
"mcpServers": {
"beads-viz": {
"command": "npx",
"args": ["-y", "beads-viz"],
"cwd": "/home/dev/projects/myapp"
}
}
}
Using a local build:
{
"mcpServers": {
"beads-viz": {
"command": "node",
"args": ["/home/dev/tools/beads-viz/dist/server/index.js"],
"cwd": "/home/dev/projects/myapp"
}
}
}
If you use nvm and node/npx isn't on PATH for GUI apps, use full paths:
{
"mcpServers": {
"beads-viz": {
"command": "/home/dev/.nvm/versions/node/v22.21.0/bin/npx",
"args": ["-y", "beads-viz"],
"cwd": "/home/dev/projects/myapp"
}
}
}
Node.js and the Beads CLI are installed natively on Windows.
Edit %APPDATA%\Claude\claude_desktop_config.json:
Using npx (recommended):
{
"mcpServers": {
"beads-viz": {
"command": "npx",
"args": ["-y", "beads-viz"],
"cwd": "C:\\Users\\you\\projects\\myapp"
}
}
}
Using a local build:
{
"mcpServers": {
"beads-viz": {
"command": "node",
"args": ["C:\\Users\\you\\tools\\beads-viz\\dist\\server\\index.js"],
"cwd": "C:\\Users\\you\\projects\\myapp"
}
}
}
Use double backslashes (
\\) in JSON paths, or forward slashes (/) — Node.js accepts both on Windows.
This is the recommended setup for WSL users. Claude Desktop runs on Windows, but your Node.js, Beads CLI, and projects live inside WSL.
The trick: use wsl.exe as the command, which bridges into WSL and runs the server there.
Edit %APPDATA%\Claude\claude_desktop_config.json:
Using npx (recommended):
{
"mcpServers": {
"beads-viz": {
"command": "wsl.exe",
"args": [
"bash", "-lc",
"cd /home/dev/projects/myapp && npx -y beads-viz"
]
}
}
}
Using a local build:
{
"mcpServers": {
"beads-viz": {
"command": "wsl.exe",
"args": [
"bash", "-lc",
"cd /home/dev/projects/myapp && node /home/dev/tools/beads-viz/dist/server/index.js"
]
}
}
}
Why bash -lc? The -l flag loads your login shell profile (~/.bashrc, ~/.profile), which sets up nvm, PATH, and other environment variables. Without it, node and bd may not be found.
Why cd ... &&? The cwd field in config.json is a Windows path and won't work inside WSL. Instead, we cd to the project directory inside the bash command.
If you have a specific WSL distro (not the default):
{
"mcpServers": {
"beads-viz": {
"command": "wsl.exe",
"args": [
"-d", "Ubuntu-24.04",
"bash", "-lc",
"cd /home/dev/projects/myapp && npx -y beads-viz"
]
}
}
}
Troubleshooting WSL:
node isn't found, check that nvm loads in ~/.bashrc (not just ~/.bash_profile)bd isn't found, verify it's on your WSL PATH: wsl.exe bash -lc "which bd"wsl.exe bash -lc "cd /home/dev/projects/myapp && node /home/dev/tools/beads-viz/dist/server/index.js"
You should see the server start (it reads from stdin, so it will hang — that's normal). Press Ctrl+C to stop.VS Code with GitHub Copilot Chat can also host MCP Apps, displaying them as interactive panels alongside the chat.
Add to your VS Code settings.json (Ctrl+, → search "mcp" → Edit in settings.json):
Using npx (recommended):
{
"github.copilot.chat.mcp.servers": {
"beads-viz": {
"command": "npx",
"args": ["-y", "beads-viz"],
"cwd": "/absolute/path/to/your/beads-project"
}
}
}
Using a local build:
{
"github.copilot.chat.mcp.servers": {
"beads-viz": {
"command": "node",
"args": ["/absolute/path/to/beads-viz/dist/server/index.js"],
"cwd": "/absolute/path/to/your/beads-project"
}
}
}
You can also add this to workspace settings (.vscode/settings.json) to scope it per project:
{
"github.copilot.chat.mcp.servers": {
"beads-viz": {
"command": "npx",
"args": ["-y", "beads-viz"],
"cwd": "${workspaceFolder}"
}
}
}
Your VS Code runs on Windows, but the project and toolchain live in WSL. Same wsl.exe bridge technique.
Add to your VS Code settings.json:
Using npx (recommended):
{
"github.copilot.chat.mcp.servers": {
"beads-viz": {
"command": "wsl.exe",
"args": [
"bash", "-lc",
"cd /home/dev/projects/myapp && npx -y beads-viz"
]
}
}
}
Using a local build:
{
"github.copilot.chat.mcp.servers": {
"beads-viz": {
"command": "wsl.exe",
"args": [
"bash", "-lc",
"cd /home/dev/projects/myapp && node /home/dev/tools/beads-viz/dist/server/index.js"
]
}
}
}
Workspace settings in WSL projects: If you open a WSL folder in VS Code (via
code .from WSL terminal or the Remote-WSL extension), VS Code may resolve paths differently. See the Remote-WSL section below.
When using the Remote - WSL extension (or the newer WSL extension), VS Code runs its extension host inside WSL. This means MCP servers configured in workspace settings run natively in WSL — no wsl.exe bridge needed.
In your WSL project's .vscode/settings.json:
Using npx (recommended):
{
"github.copilot.chat.mcp.servers": {
"beads-viz": {
"command": "npx",
"args": ["-y", "beads-viz"],
"cwd": "${workspaceFolder}"
}
}
}
Using a local build:
{
"github.copilot.chat.mcp.servers": {
"beads-viz": {
"command": "node",
"args": ["/home/dev/tools/beads-viz/dist/server/index.js"],
"cwd": "${workspaceFolder}"
}
}
}
This is the cleanest approach for WSL users who already use the Remote-WSL workflow. No bridge, no path translation — everything runs natively inside WSL.
| Field | Type | Description |
|---|---|---|
command |
string | Executable to run (npx, beads-viz, node, or wsl.exe) |
args |
string[] | Arguments passed to the command |
cwd |
string | Working directory — the server discovers the Beads project from here |
env |
object | Optional environment variables to set |
The server discovers the Beads project by walking up from cwd to find .beads/config.yaml. If no project is found, the visualize-tasks tool returns an error message.
Once configured, restart your host application (Claude Desktop or VS Code) and ask the agent:
"Show me the task graph"
The agent calls the visualize-tasks tool, which returns a task summary and opens the interactive visualization in a sandboxed iframe.
| View | Description |
|---|---|
| DAG | Dependency graph with ELK.js layered layout. Nodes colored by phase, edges show dependencies. Click a node to see details. |
| List | Status-grouped task list: Ready (unblocked), In Progress, Blocked, Done. Click a row for details. |
| Stats | Progress ring, status breakdown, phase completion bars, 7-day velocity chart. |
| Key | Action |
|---|---|
Escape |
Close the task detail drawer |
1 |
Switch to DAG view |
2 |
Switch to List view |
3 |
Switch to Stats view |
| Tool | Visibility | Description |
|---|---|---|
visualize-tasks |
Agent (model) | Opens the visualization. Returns task summary + UI reference. |
poll-tasks |
App only | Returns current task data. Called by the UI every 3 seconds. |
show-task |
App only | Returns detailed info for a single task (description, comments, deps). |
"App only" tools are called by the UI iframe via the MCP Apps SDK, not by the agent.
npm run dev
Opens the Vite dev server at http://localhost:5173. The UI runs in standalone mode — no MCP host, no data. The bridge logs a warning and the UI shows an empty state. Useful for styling and layout work.
Build the server, then run it manually:
npm run build
cd /path/to/your/beads-project
node /path/to/beads-viz/dist/server/index.js
The server communicates via stdio (JSON-RPC over stdin/stdout). To test tool calls, you'd need an MCP client or the MCP Inspector.
src/
├── server/ # MCP server (Node.js, compiled with tsc)
│ ├── index.ts # Entry: McpServer + StdioServerTransport
│ ├── 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, bundled with Vite)
├── App.svelte # Root: view switching, layout, keyboard
├── main.ts # Svelte mount + MCP bridge init
├── app.css # CSS variables, theme, 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 compact node cards
│ ├── 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 Apps SDK bridge
└── types.ts # UI-side TypeScript types
| Component | Technology |
|---|---|
| MCP server | TypeScript, @modelcontextprotocol/sdk, stdio transport |
| MCP App bridge | @modelcontextprotocol/ext-apps (host theme, tool calls) |
| UI framework | Svelte 5 (runes + stores) |
| DAG layout | ELK.js (layered algorithm) |
| Build | Vite + vite-plugin-singlefile (single HTML output) |
| Bundled UI | ~557 KB gzipped (ELK.js is ~180 KB of that) |
visualize-tasks — server runs bd list --all --json_meta.ui.resourceUri: "ui://beads-viz"ui://beads-viz in a sandboxed iframe, passing the tool result to the UIontoolresult callbackpoll-tasks every 3 seconds via app.callServerTool() for live updatesonhostcontextchangedNodes are colored by their DAG layer using a 9-phase palette (ported from Hangar IDE):
| Phase | Color | Hex |
|---|---|---|
| P1 | Cyan | #38bdf8 |
| P2 | Indigo | #818cf8 |
| P3 | Purple | #c084fc |
| P4 | Pink | #f472b6 |
| P5 | Orange | #fb923c |
| P6 | Yellow | #facc15 |
| P7 | Emerald | #34d399 |
| P8 | Red | #f87171 |
| P9 | Slate | #94a3b8 |
Layer-to-phase mapping: phase = (layer % 9) + 1. Deep graphs wrap around.
The server walks up from cwd looking for .beads/config.yaml. Make sure:
cwd points to a directory inside a Beads project.beads/config.yaml file existsThe server calls bd via execFile. Ensure:
bd is installed and on PATHbd must be on the WSL PATH (not Windows PATH)which bd (or wsl.exe bash -lc "which bd" from Windows)The CLI has a 10-second timeout. This can happen with very large projects. Check:
bd list --all --json runs successfully from the command linenpm run build completed without errorsdist/index.html exists (the UI bundle)npm run dev), empty state is expected — no MCP hostYour login shell profile isn't loading. Ensure:
~/.bashrc (not just ~/.bash_profile or ~/.zshrc)bash -lc flag is present in the args (the -l loads the profile)wsl.exe bash -lc "which node && which bd"The UI adapts to the host's theme via onhostcontextchanged. If colors look wrong:
[data-theme] attribute on <html>MIT