Skip to main content
Glama

get_graph_neighbors

Retrieve notes within specified link-hops of a starting note in Obsidian vaults, enabling graph traversal and link analysis.

Instructions

Get notes within N link-hops of a given note

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
pathYesThe starting note path (relative to vault root)
depthNoMaximum number of link-hops (1-5)
directionNoDirection to traverse: inbound (backlinks), outbound (outlinks), or bothboth

Implementation Reference

  • The 'get_graph_neighbors' tool is registered and implemented in this block, using BFS to traverse the link graph and return neighbor notes within a specified depth and direction.
    server.registerTool(
      "get_graph_neighbors",
      {
        description: "Get notes within N link-hops of a given note",
        inputSchema: {
          path: z.string().min(1).describe("The starting note path (relative to vault root)"),
          depth: z
            .number()
            .int()
            .min(1)
            .max(5)
            .optional()
            .default(1)
            .describe("Maximum number of link-hops (1-5)"),
          direction: z
            .enum(["both", "inbound", "outbound"])
            .optional()
            .default("both")
            .describe("Direction to traverse: inbound (backlinks), outbound (outlinks), or both"),
        },
      },
      async ({ path: startPath, depth, direction }) => {
        try {
          const graph = await buildLinkGraph(vaultPath);
    
          // Resolve the start path
          const startNormalized = startPath.replace(/\.md$/i, "").toLowerCase();
          let resolvedStart: string | null = null;
    
          for (const notePath of graph.allNotes) {
            const noteNormalized = notePath.replace(/\.md$/i, "").toLowerCase();
            if (noteNormalized === startNormalized) {
              resolvedStart = notePath;
              break;
            }
          }
    
          if (!resolvedStart) {
            // Try basename matching
            const startBasename = startNormalized.split("/").pop() ?? startNormalized;
            for (const notePath of graph.allNotes) {
              const noteBasename = notePath
                .replace(/\.md$/i, "")
                .split("/")
                .pop()
                ?.toLowerCase();
              if (noteBasename === startBasename) {
                resolvedStart = notePath;
                break;
              }
            }
          }
    
          if (!resolvedStart) {
            return errorResult(`No note found matching path: ${startPath}`);
          }
    
          // BFS traversal
          const visited = new Map<string, GraphNeighbor>();
          const queue: { path: string; currentDepth: number }[] = [
            { path: resolvedStart, currentDepth: 0 },
          ];
          visited.set(resolvedStart, {
            path: resolvedStart,
            depth: 0,
            direction: "both",
          });
    
          while (queue.length > 0) {
            const { path: currentPath, currentDepth } = queue.shift()!;
            if (currentDepth >= depth) continue;
    
            const neighbors: { path: string; dir: "inbound" | "outbound" }[] = [];
    
            if (direction === "outbound" || direction === "both") {
              const outs = graph.outlinks.get(currentPath);
              if (outs) {
                for (const target of outs) {
                  neighbors.push({ path: target, dir: "outbound" });
                }
              }
            }
    
            if (direction === "inbound" || direction === "both") {
              const ins = graph.backlinks.get(currentPath);
              if (ins) {
                for (const source of ins) {
                  neighbors.push({ path: source, dir: "inbound" });
                }
              }
            }
    
            for (const neighbor of neighbors) {
              if (!visited.has(neighbor.path)) {
                const neighborInfo: GraphNeighbor = {
                  path: neighbor.path,
                  depth: currentDepth + 1,
                  direction: neighbor.dir,
                };
                visited.set(neighbor.path, neighborInfo);
                queue.push({ path: neighbor.path, currentDepth: currentDepth + 1 });
              }
            }
          }
    
          // Remove the start node from results
          visited.delete(resolvedStart);
    
          if (visited.size === 0) {
            return {
              content: [
                {
                  type: "text" as const,
                  text: `No neighbors found for: ${resolvedStart} (depth: ${depth}, direction: ${direction})`,
                },
              ],
            };
          }
    
          // Group by depth level for tree-like output
          const byDepth = new Map<number, GraphNeighbor[]>();
          for (const neighbor of visited.values()) {
            if (!byDepth.has(neighbor.depth)) {
              byDepth.set(neighbor.depth, []);
            }
            byDepth.get(neighbor.depth)!.push(neighbor);
          }
    
          const lines: string[] = [
            `Graph neighbors of: ${resolvedStart}`,
            `Direction: ${direction} | Max depth: ${depth} | Found: ${visited.size} note(s)\n`,
            resolvedStart,
          ];
    
          const sortedDepths = [...byDepth.keys()].sort((a, b) => a - b);
          for (const d of sortedDepths) {
            const neighbors = byDepth.get(d)!;
            neighbors.sort((a, b) => a.path.localeCompare(b.path));
    
            for (const neighbor of neighbors) {
              const indent = "  ".repeat(d);
              const arrow =
                neighbor.direction === "inbound"
                  ? "←"
                  : neighbor.direction === "outbound"
                    ? "→"
                    : "↔";
              lines.push(`${indent}${arrow} ${neighbor.path} (depth ${d})`);
            }
          }
    
          return { content: [{ type: "text" as const, text: lines.join("\n") }] };
        } catch (err) {
          console.error("get_graph_neighbors error:", err);
          return errorResult(`Error getting graph neighbors: ${err instanceof Error ? err.message : String(err)}`);
        }
      },
    );

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rps321321/obsidian-mcp-pro'

If you have feedback or need assistance with the MCP directory API, please join our Discord server