Read Canvas
read_canvasRead an Obsidian canvas file and generate a structured summary of all nodes and edges with positions, sizes, and content previews to inspect canvas structure before adding new elements.
Instructions
Read an Obsidian canvas file (.canvas, JSON format) and return a human-readable summary of its structure: every node with id, type, position, size, and content preview, plus every edge with source/target node ids and optional label. Use to inspect or navigate a canvas before calling add_canvas_node or add_canvas_edge.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Relative path from vault root to the .canvas file (e.g., 'boards/roadmap.canvas') |
Implementation Reference
- src/tools/canvas.ts:46-122 (handler)The read_canvas tool handler. Registered via server.registerTool('read_canvas', ...). Reads a .canvas file using readCanvasFile helper, formats a human-readable summary of nodes (id, type, position, size, content preview) and edges (source/target with optional label and side info), and returns it as text content.
server.registerTool( "read_canvas", { title: "Read Canvas", description: "Read an Obsidian canvas file (.canvas, JSON format) and return a human-readable summary of its structure: every node with id, type, position, size, and content preview, plus every edge with source/target node ids and optional label. Use to inspect or navigate a canvas before calling add_canvas_node or add_canvas_edge.", annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false, }, inputSchema: { path: z .string() .min(1) .describe("Relative path from vault root to the .canvas file (e.g., 'boards/roadmap.canvas')"), }, }, async ({ path: canvasPath }) => { try { const data = await readCanvasFile(vaultPath, canvasPath); const lines: string[] = []; lines.push(`Canvas: ${canvasPath}`); lines.push(`Nodes: ${data.nodes.length} | Edges: ${data.edges.length}`); lines.push(""); if (data.nodes.length > 0) { lines.push("--- Nodes ---"); for (const node of data.nodes) { const pos = `(${node.x}, ${node.y})`; const size = `${node.width}x${node.height}`; let preview = ""; if (node.type === "text" && node.text) { preview = node.text.length > 100 ? node.text.slice(0, 100) + "..." : node.text; } else if (node.type === "file" && node.file) { preview = node.file; } else if (node.type === "link" && node.url) { preview = node.url; } else if (node.type === "group" && node.label) { preview = `Group: ${node.label}`; } lines.push(` [${node.id}] type=${node.type} pos=${pos} size=${size}`); if (preview) { lines.push(` content: ${preview}`); } if (node.color) { lines.push(` color: ${node.color}`); } } lines.push(""); } if (data.edges.length > 0) { lines.push("--- Edges ---"); for (const edge of data.edges) { const label = edge.label ? ` [${edge.label}]` : ""; const sides = [ edge.fromSide ? `from-side=${edge.fromSide}` : "", edge.toSide ? `to-side=${edge.toSide}` : "", ].filter(Boolean).join(" "); const sideInfo = sides ? ` (${sides})` : ""; lines.push(` ${edge.fromNode} -> ${edge.toNode}${label}${sideInfo}`); } } return { content: [{ type: "text" as const, text: lines.join("\n") }] }; } catch (err) { log.error("read_canvas failed", { tool: "read_canvas", err: err as Error }); return errorResult(`Error reading canvas: ${sanitizeError(err)}`); } }, ); - src/tools/canvas.ts:46-48 (registration)Registration of the 'read_canvas' tool on the MCP server within registerCanvasTools().
server.registerTool( "read_canvas", { - src/tools/canvas.ts:57-62 (schema)Input schema for read_canvas: requires a 'path' string (relative .canvas file path from vault root), validated via zod.
inputSchema: { path: z .string() .min(1) .describe("Relative path from vault root to the .canvas file (e.g., 'boards/roadmap.canvas')"), }, - src/lib/vault.ts:841-861 (helper)The readCanvasFile helper function. Resolves the vault-relative path to an absolute path, reads the file as UTF-8, parses JSON, and returns a CanvasData object with nodes and edges arrays.
export async function readCanvasFile( vaultPath: string, relativePath: string, ): Promise<CanvasData> { const fullPath = await resolveVaultPathSafe(vaultPath, relativePath); const content = await fs.readFile(fullPath, "utf-8"); let parsed: unknown; try { parsed = JSON.parse(content); } catch { throw new Error(`Invalid canvas file (malformed JSON): ${relativePath}`); } const data = parsed as Record<string, unknown>; if (!Array.isArray(data.nodes)) { return { nodes: [], edges: [] }; } return { nodes: data.nodes as CanvasData["nodes"], edges: Array.isArray(data.edges) ? data.edges as CanvasData["edges"] : [], }; } - src/types.ts:43-71 (schema)Type definitions for CanvasNode, CanvasEdge, and CanvasData interfaces used by the read_canvas tool.
export interface CanvasNode { id: string; type: "text" | "file" | "link" | "group"; x: number; y: number; width: number; height: number; text?: string; file?: string; url?: string; label?: string; color?: string; } export interface CanvasEdge { id: string; fromNode: string; toNode: string; fromSide?: "top" | "right" | "bottom" | "left"; toSide?: "top" | "right" | "bottom" | "left"; fromEnd?: "none" | "arrow"; toEnd?: "none" | "arrow"; label?: string; color?: string; } export interface CanvasData { nodes: CanvasNode[]; edges: CanvasEdge[];