Traverse v0.1
Interactive code walkthrough diagrams
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod/v4"; import { generateViewerHTML } from "./template.ts"; import type { WalkthroughDiagram } from "./types.ts"; import { initDb, loadAllDiagrams, saveDiagram, deleteDiagramFromDb, generateId, getSharedUrl, saveSharedUrl } from "./storage.ts"; import { generateOgImage, generateIndexOgImage } from "./og.ts"; import { loadConfig } from "./config.ts"; const config = loadConfig(); const PORT = config.port; const MODE = config.mode; const VERSION = await Bun.$`git rev-parse --short HEAD`.text().then(s => s.trim()).catch(async () => { const pkg = await Bun.file(import.meta.dir + "/../package.json").json(); return `v${pkg.version}`; }); const GIT_HASH = await Bun.$`git rev-parse HEAD`.text().then(s => s.trim()).catch(() => ""); const GITHUB_REPO = await Bun.$`git remote get-url origin`.text().then(url => { url = url.trim(); // Convert SSH URLs: git@github.com:user/repo.git -> https://github.com/user/repo const sshMatch = url.match(/^git@github\.com:(.+?)(?:\.git)?$/); if (sshMatch) return `https://github.com/${sshMatch[1]}`; // Convert HTTPS URLs: https://github.com/user/repo.git -> https://github.com/user/repo const httpsMatch = url.match(/^https:\/\/github\.com\/(.+?)(?:\.git)?$/); if (httpsMatch) return `https://github.com/${httpsMatch[1]}`; return ""; }).catch(() => ""); initDb(); // Load persisted diagrams const diagrams = loadAllDiagrams(); // --- Web server for serving interactive diagrams --- let isClient = false; try { Bun.serve({ port: PORT, async fetch(req) { const url = new URL(req.url); // OG image route — must be matched before /diagram/:id const ogMatch = url.pathname.match(/^\/diagram\/([\w-]+)\/og\.png$/); if (ogMatch) { const id = ogMatch[1]!; const diagram = diagrams.get(id); if (!diagram) { return new Response("Not found", { status: 404 }); } const nodeNames = Object.values(diagram.nodes).map(n => n.title); const png = await generateOgImage(id, diagram.summary, nodeNames); return new Response(png, { headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=86400", }, }); } const diagramMatch = url.pathname.match(/^\/diagram\/([\w-]+)$/); if (diagramMatch) { const id = diagramMatch[1]!; const diagram = diagrams.get(id); if (!diagram) { return new Response(generate404HTML("Diagram not found", "This diagram doesn't exist or may have expired."), { status: 404, headers: { "Content-Type": "text/html; charset=utf-8" }, }); } const existingShareUrl = getSharedUrl(id); return new Response(generateViewerHTML(diagram, VERSION, process.cwd(), { mode: MODE, shareServerUrl: config.shareServerUrl, diagramId: id, existingShareUrl: existingShareUrl ?? undefined, baseUrl: url.origin, }), { headers: { "Content-Type": "text/html; charset=utf-8" }, }); } // DELETE /api/diagrams/:id const apiMatch = url.pathname.match(/^\/api\/diagrams\/([\w-]+)$/); if (apiMatch && req.method === "DELETE") { const id = apiMatch[1]!; if (!diagrams.has(id)) { return Response.json({ error: "not found" }, { status: 404 }); } diagrams.delete(id); deleteDiagramFromDb(id); return Response.json({ ok: true, id }); } // POST /api/diagrams/:id/shared-url — save a shared URL for a local diagram const sharedUrlMatch = url.pathname.match(/^\/api\/diagrams\/([\w-]+)\/shared-url$/); if (sharedUrlMatch && req.method === "POST") { const id = sharedUrlMatch[1]!; try { const body = await req.json() as { url: string }; if (!body.url) { return Response.json({ error: "missing required field: url" }, { status: 400 }); } saveSharedUrl(id, body.url); return Response.json({ ok: true, id, url: body.url }); } catch { return Response.json({ error: "invalid JSON body" }, { status: 400 }); } } // GET /api/diagrams/:id/shared-url — retrieve a stored shared URL if (sharedUrlMatch && req.method === "GET") { const id = sharedUrlMatch[1]!; const sharedUrl = getSharedUrl(id); if (!sharedUrl) { return Response.json({ url: null }); } return Response.json({ url: sharedUrl }); } // POST /api/diagrams — accept diagrams from remote or sibling instances if (url.pathname === "/api/diagrams" && req.method === "POST") { try { const body = await req.json() as WalkthroughDiagram; if (!body.code || !body.summary || !body.nodes) { return Response.json({ error: "missing required fields: code, summary, nodes" }, { status: 400 }); } const id = generateId(); const diagram: WalkthroughDiagram = { code: body.code, summary: body.summary, nodes: body.nodes, githubRepo: body.githubRepo || undefined, githubRef: body.githubRef || undefined, createdAt: new Date().toISOString(), }; diagrams.set(id, diagram); saveDiagram(id, diagram); const diagramUrl = `${url.origin}/diagram/${id}`; return Response.json({ id, url: diagramUrl }, { status: 201, headers: { "Access-Control-Allow-Origin": "*", }, }); } catch { return Response.json({ error: "invalid JSON body" }, { status: 400 }); } } // OPTIONS /api/diagrams — CORS preflight if (url.pathname === "/api/diagrams" && req.method === "OPTIONS") { return new Response(null, { status: 204, headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }, }); } if (url.pathname === "/icon.svg") { return new Response(Bun.file(import.meta.dir + "/../icon.svg"), { headers: { "Content-Type": "image/svg+xml" }, }); } // Index OG image if (url.pathname === "/og.png") { const png = await generateIndexOgImage(MODE, diagrams.size); return new Response(png, { headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=3600", }, }); } // List available diagrams if (url.pathname === "/") { const html = MODE === "server" ? generateServerIndexHTML(diagrams.size, VERSION, url.origin) : generateLocalIndexHTML(diagrams, VERSION, url.origin); return new Response(html, { headers: { "Content-Type": "text/html; charset=utf-8" }, }); } return new Response(generate404HTML("Page not found", "There's nothing at this URL."), { status: 404, headers: { "Content-Type": "text/html; charset=utf-8" }, }); }, }); } catch { isClient = true; console.error(`Web server already running on port ${PORT}, running in client mode`); } // --- MCP Server (local mode only) --- if (MODE === "local") { const server = new McpServer({ name: "traverse", version: "0.1.0", }); const nodeMetadataSchema = z.object({ title: z.string(), description: z.string(), links: z .array(z.object({ label: z.string(), url: z.string() })) .optional(), codeSnippet: z.string().optional(), }); server.registerTool("walkthrough_diagram", { title: "Walkthrough Diagram", description: `Render an interactive Mermaid diagram where users can click nodes to see details. BEFORE calling this tool, deeply explore the codebase: 1. Use search/read tools to find key files, entry points, and architecture patterns 2. Trace execution paths and data flow between components 3. Read source files — don't guess from filenames Then build the diagram: - Use \`flowchart TB\` with plain text labels, no HTML or custom styling - 5-12 nodes at the right abstraction level (not too granular, not too high-level) - Node keys must match Mermaid node IDs exactly - Descriptions: 2-3 paragraphs of markdown per node. Write for someone who has never seen this codebase — explain what the component does, how it works, and why it matters. Use \`code spans\` for identifiers and markdown headers to organize longer explanations - Links: include file:line references from your exploration - Code snippets: key excerpts (under 15 lines) showing the most important or representative code`, inputSchema: z.object({ code: z.string(), summary: z.string(), nodes: z.record(z.string(), nodeMetadataSchema), }), }, async ({ code, summary, nodes }) => { let diagramUrl: string; if (isClient) { // POST diagram to the existing web server instance const res = await fetch(`http://localhost:${PORT}/api/diagrams`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code, summary, nodes, githubRepo: GITHUB_REPO || undefined, githubRef: GIT_HASH || undefined }), }); if (!res.ok) { return { content: [{ type: "text", text: `Failed to send diagram to server: ${res.statusText}` }], }; } const data = await res.json() as { id: string; url: string }; diagramUrl = data.url; } else { const id = generateId(); const diagram: WalkthroughDiagram = { code, summary, nodes, githubRepo: GITHUB_REPO || undefined, githubRef: GIT_HASH || undefined, createdAt: new Date().toISOString(), }; diagrams.set(id, diagram); saveDiagram(id, diagram); diagramUrl = `http://localhost:${PORT}/diagram/${id}`; } return { content: [ { type: "text", text: `Interactive diagram ready.\n\nOpen in browser: ${diagramUrl}\n\nClick nodes in the diagram to explore details about each component.`, }, ], }; }); // Connect MCP server to stdio transport const transport = new StdioServerTransport(); await server.connect(transport); } function generate404HTML(title: string, message: string): string { return `
No diagrams yet.
Use the walkthrough_diagram MCP tool to create one.
Interactive code walkthrough diagrams
Interactive code walkthrough diagrams, shareable with anyone. Powered by an MCP server you run locally.