Give Claude eyes. Ask it to visualize anything — diagrams, documents, data, math — and it renders instantly in a native window on your desktop.
Lunette is an MCP server that connects Claude (or any MCP-compatible assistant) to a Tauri desktop viewer. When Claude calls the visualize tool, Lunette opens a native window and renders the content. No copy-pasting, no browser tabs, no file juggling.
npm install
npm run tauri build
sudo ln -sf "$(pwd)/src-tauri/target/release/lunette" /usr/local/bin/lunettelunette --install-skillThis installs two things:
| Component | Location | Purpose |
|---|---|---|
| MCP server | ~/.lunette/mcp/server.js |
Exposes the visualize tool that Claude calls |
| Claude Code skill | ~/.claude/skills/lunette-visualize.md |
Teaches Claude when and how to use Lunette |
The installer prints the config snippet. Add it to your project's .mcp.json or ~/.claude.json for global access:
{
"mcpServers": {
"lunette": {
"type": "stdio",
"command": "node",
"args": ["~/.lunette/mcp/server.js"]
}
}
}You: "Draw a sequence diagram of the OAuth flow"
Claude: → renders a Mermaid diagram in Lunette
You: "Show me the API response as JSON"
Claude: → renders an interactive JSON tree in Lunette
You: "Render the Schrödinger equation"
Claude: → renders LaTeX math in Lunette
Claude will ask for your confirmation before opening Lunette, then the content appears instantly in a native window.
Claude ──▶ MCP Server ──▶ Lunette ──▶ Native Window
│
├── writes content to temp file
└── opens via lunette:// deep link
The MCP server exposes a single visualize tool. Claude calls it with the content and an optional format hint. Lunette auto-detects the format and renders it in the appropriate viewer.
Lunette ships both a skill and an MCP server — they work together:
-
The MCP server (
~/.lunette/mcp/server.js) is the tool itself. It exposes avisualizefunction that writes content to a temp file and opens it in Lunette via deep link. Any MCP-compatible client can call it. -
The skill (
~/.claude/skills/lunette-visualize.md) is Claude Code-specific. It teaches Claude when to use the tool (e.g. after generating a Mermaid diagram), how to format the content, and when not to (e.g. trivial one-liners). Without the skill, Claude can still call the tool, but may not use it proactively.
Both are installed in one step with lunette --install-skill.
| Format | What Claude generates | What you see |
|---|---|---|
| Mermaid | graph TD; A-->B |
Flowcharts, sequence diagrams, Gantt charts, mind maps |
| Markdown | # Title\n**bold** text |
Styled documentation with syntax-highlighted code |
| JSON | {"key": "value"} |
Interactive tree with expand/collapse |
| LaTeX | $$E = mc^2$$ |
Beautifully rendered math equations |
| Excalidraw | {"elements": [...], "appState": {...}} |
Hand-drawn style sketches and whiteboards |
- Brainstorming — Ask Claude to map out ideas as diagrams, refine them iteratively
- Architecture — Have Claude generate system diagrams from descriptions or code
- Data exploration — Pipe API responses through Claude, see them as structured JSON trees
- Documentation — Preview markdown output before committing
- Math / Research — Render equations and formulas as you discuss them
Lunette is not limited to Claude. Pipe any command's output directly:
# Diagrams
echo "graph TD; A[Start] --> B[End]" | lunette
cat architecture.mmd | lunette
# Documentation
cat README.md | lunette
# Data
curl -s https://api.example.com/data | lunette
# Math
echo '$$E = mc^2$$' | lunetteOpen content from scripts, browsers, or automation:
# Base64
open "lunette://?data=$(echo '# Hello' | base64)"
# Compressed (zlib + base64)
DATA=$(python3 -c "
import zlib, base64
raw = b'graph TD\nA-->B'
print(base64.b64encode(zlib.compress(raw)).decode())
")
open "lunette://?data=$DATA"
# Temp file
echo "# Report" > /tmp/report.md
open "lunette://?file=/tmp/report.md"Lunette runs as a single instance. New content updates the existing window:
echo "graph TD; A-->B" | lunette # Opens Lunette
echo "# New content" | lunette # Updates the same window┌─────────────────────────────────────────────┐
│ Entry Points │
│ ┌────────────┐ ┌────────┐ ┌───────────┐ │
│ │ MCP Server │ │ Pipe │ │ Deep Link │ │
│ └─────┬──────┘ └───┬────┘ └─────┬─────┘ │
│ └─────────────┼──────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │Content Detector │ Rust │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Tauri IPC │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ Frontend (React) │ │
│ │ ViewerRouter → Lazy Viewer │ │
│ │ ┌─────────┬──────────┬────────┐ │ │
│ │ │ Mermaid │ Markdown │ JSON │ │ │
│ │ ├─────────┼──────────┼────────┤ │ │
│ │ │ LaTeX │Excalidraw│ Plugin │ │ │
│ │ └─────────┴──────────┴────────┘ │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────┘
content_detector.rs— Heuristic chain: Plugin → Mermaid → Excalidraw → JSON → LaTeX → Markdown → Unrecognizedpipe_handler.rs— Reads stdin, handles base64 decodingdeep_link_handler.rs— Parseslunette://URLs (base64, zlib, temp file)ipc.rs— Single-instance enforcement via Unix socket / Windows named pipeplugin_loader.rs— Loads JS plugins from~/.lunette/plugins/
ViewerRouter.tsx— Listens to Tauri events, dispatches to lazy-loaded viewersMermaidViewer.tsx— Renders via the Mermaid libraryMarkdownViewer.tsx— Renders via Marked + highlight.jsJsonViewer.tsx— Interactive tree via @uiw/react-json-viewLatexViewer.tsx— Math rendering via KaTeXExcalidrawViewer.tsx— Full Excalidraw canvasPluginViewer.tsx— Sandboxed iframe for third-party renderers
src/mcp/src/index.ts— Exposes thevisualizetool, writes content to a temp file, opens Lunette via deep link
- Node.js 20+
- Rust stable
- Tauri v2 system dependencies (platform guide)
npm install
npm run tauri dev# Frontend (Vitest)
npm test
# Backend (Cargo)
cargo test --manifest-path src-tauri/Cargo.toml
# TypeScript typecheck
npx tsc --noEmit
# Manual test suite (interactive)
./test.shlunette/
├── src/ # React frontend
│ ├── components/ # Viewer components
│ ├── mcp/ # MCP server (Node.js)
│ └── theme.ts # Design tokens
├── src-tauri/ # Rust backend
│ └── src/
│ ├── content_detector.rs
│ ├── pipe_handler.rs
│ ├── deep_link_handler.rs
│ ├── ipc.rs
│ └── lib.rs # App setup + pipeline
├── fixtures/ # Test data
├── docs/
│ ├── assets/ # README images
│ └── videos/ # Remotion demo video project
└── .github/workflows/ # CI + Release pipelines
Runs on Ubuntu: TypeScript typecheck → frontend build → Vitest → Cargo test → Clippy.
Triggered manually via GitHub Actions:
- Actions → Release → Run workflow → enter version (e.g.
0.2.0) - Pipeline bumps the version in all manifests, tags, and builds for all platforms
- Artifacts (
.dmg,.msi,.deb,.AppImage) uploaded to a draft GitHub Release - Review and publish
MIT
