obsidian_traverse_graph
Walk the Obsidian note graph from a starting note through forward, backward, or both directions up to N hops to discover linked notes.
Instructions
Walk the note graph from a starting note for N hops in forward, backward, or both directions.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| vault | No | Optional configured vault name. Defaults to the server default vault. | |
| start | Yes | Vault-relative path. Absolute paths and traversal are rejected. | |
| depth | No | ||
| direction | No | both | |
| limit | No |
Implementation Reference
- src/tools.ts:751-763 (registration)Registration of the obsidian_traverse_graph tool with schema (vault, start path, depth, direction, limit) and a handler that calls traverseGraph from graph.ts
tool( "obsidian_traverse_graph", "Walk the note graph from a starting note for N hops in forward, backward, or both directions.", { vault: vaultArg, start: pathArg, depth: z.number().int().min(1).max(6).optional().default(2), direction: z.enum(["forward", "backward", "both"]).optional().default("both"), limit: z.number().int().min(1).max(500).optional().default(100), }, async (args) => traverseGraph(buildGraph(await loadNotes(vaults, args.vault)), args.start, args), { readOnlyHint: true }, ); - src/tools.ts:754-760 (schema)Input schema for the obsidian_traverse_graph tool: vault, start path, depth (1-6), direction (forward/backward/both), limit (1-500)
{ vault: vaultArg, start: pathArg, depth: z.number().int().min(1).max(6).optional().default(2), direction: z.enum(["forward", "backward", "both"]).optional().default("both"), limit: z.number().int().min(1).max(500).optional().default(100), }, - src/graph.ts:79-117 (handler)Core handler for obsidian_traverse_graph. Performs BFS traversal of the vault link graph from a starting note, respecting depth, direction (forward/backward/both), and limit. Returns visited nodes and traversed edges.
export function traverseGraph( graph: VaultGraph, startPath: string, options: { depth: number; direction: "forward" | "backward" | "both"; limit: number }, ): { nodes: GraphNode[]; edges: GraphEdge[] } { const start = resolveGraphPath(graph, startPath); if (!start) return { nodes: [], edges: [] }; const seen = new Set<string>([start.path]); const edgeSeen = new Set<string>(); const queue: Array<{ path: string; depth: number }> = [{ path: start.path, depth: 0 }]; const resultEdges: GraphEdge[] = []; while (queue.length > 0 && seen.size < options.limit) { const current = queue.shift(); if (!current || current.depth >= options.depth) continue; const edges = graph.edges.filter((edge) => { if (edge.unresolved) return false; if (options.direction === "forward") return edge.source === current.path; if (options.direction === "backward") return edge.target === current.path; return edge.source === current.path || edge.target === current.path; }); for (const edge of edges) { const key = `${edge.source}->${edge.target}:${edge.line}`; if (!edgeSeen.has(key)) { edgeSeen.add(key); resultEdges.push(edge); } const next = edge.source === current.path ? edge.target : edge.source; if (!seen.has(next)) { seen.add(next); queue.push({ path: next, depth: current.depth + 1 }); } if (seen.size >= options.limit) break; } } return { nodes: [...seen].map((nodePath) => graph.byPath.get(nodePath)).filter((node): node is GraphNode => Boolean(node)), edges: resultEdges, }; } - src/graph.ts:151-166 (helper)Helper used by traverseGraph to resolve a user-provided path to a GraphNode, trying the path as-is, with .md extension, basename-only, and stem matching.
export function resolveGraphPath(graph: VaultGraph, input: string): GraphNode | null { const clean = normalizeNoteTarget(input); const candidates = [ clean, clean.endsWith(".md") ? clean : `${clean}.md`, path.posix.basename(clean), `${path.posix.basename(clean)}.md`, ]; for (const candidate of candidates) { const direct = graph.byPath.get(candidate); if (direct) return direct; } const stem = path.posix.basename(clean).replace(/\.md$/i, "").toLowerCase(); const found = [...graph.byPath.values()].find((node) => noteStem(node.path).toLowerCase() === stem); return found ?? null; }