Skip to main content
Glama

Shortest path between two memories

memory_path
Read-onlyIdempotent

Find the shortest path between two memories to trace cause-effect chains, supersession history, or dependency lineage. Returns chain of IDs and edge types, or 'no path' if unreachable.

Instructions

BFS shortest path between two memories — returns the chain of ids + edge types, or a no path message when unreachable within max_hops. Read-only. Use after memory_search (with both endpoints known) to trace cause-effect chains, supersession history, or dependency lineage.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
from_idYesStarting memory id.
to_idYesDestination memory id.
max_hopsNoMaximum BFS depth (1-10). Default 4. Returns `no path` if the destination is further than `max_hops` from the start.
edge_typesNoOptional whitelist of edge types to follow. Omit to allow all types.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
foundYes`true` when a path exists within `max_hops`; `false` otherwise (in which case `path` is empty and `message` carries the reason).
hopsYesNumber of edges in the path. 0 when `from_id === to_id`.
pathYesOrdered list of nodes from `from_id` to `to_id`, each annotated with the edge type that takes you to the next node.
messageNoHuman-readable failure reason when `found=false` (e.g. `No path from <a> to <b> within <n> hops`).

Implementation Reference

  • Core handler that finds the shortest path between two memories using BFS on the edges graph, returning both a text representation and structured payload.
    export async function findMemoryPath(
      memRepo: MemoriesRepo,
      edgesRepo: EdgesRepo,
      params: MemoryPathParams,
    ): Promise<{ text: string; structured: MemoryPathResult }> {
      const maxHops = params.max_hops ?? 4;
    
      const path = edgesRepo.shortestPath(params.from_id, params.to_id, maxHops, params.edge_types);
      if (!path) {
        const message = `No path from ${params.from_id} to ${params.to_id} within ${maxHops} hops`;
        return {
          text: message,
          structured: { found: false, hops: 0, path: [], message },
        };
      }
    
      if (path.length === 1) {
        const mem = memRepo.getById(path[0]);
        const title = mem ? mem.title : path[0];
        return {
          text: `${path[0]} "${title}"`,
          structured: {
            found: true,
            hops: 0,
            path: [{ id: String(path[0]), title: String(title) }],
          },
        };
      }
    
      const parts: string[] = [];
      const structuredNodes: MemoryPathResult["path"] = [];
    
      for (let i = 0; i < path.length; i++) {
        const nodeId = path[i];
        const mem = memRepo.getById(nodeId);
        const title = mem ? mem.title : nodeId;
        parts.push(`${nodeId} "${title}"`);
    
        let edgeTypeToNext: EdgeType | undefined;
        if (i < path.length - 1) {
          const nextId = path[i + 1];
          const outEdges = edgesRepo.outgoing(nodeId, params.edge_types);
          const matchingOut = outEdges.find(e => e.to_id === nextId);
          if (matchingOut) {
            parts.push(`→ ${matchingOut.edge_type} →`);
            edgeTypeToNext = matchingOut.edge_type;
          } else {
            const inEdges = edgesRepo.incoming(nodeId, params.edge_types);
            const matchingIn = inEdges.find(e => e.from_id === nextId);
            if (matchingIn) {
              parts.push(`→ ${matchingIn.edge_type} →`);
              edgeTypeToNext = matchingIn.edge_type;
            } else {
              parts.push(`→`);
            }
          }
        }
    
        structuredNodes.push({
          id: String(nodeId),
          title: String(title),
          ...(edgeTypeToNext ? { edge_type_to_next: edgeTypeToNext } : {}),
        });
      }
    
      return {
        text: parts.join(" "),
        structured: {
          found: true,
          hops: path.length - 1,
          path: structuredNodes,
        },
      };
    }
  • Input parameter types for the memory_path tool.
    export interface MemoryPathParams {
      from_id: string;
      to_id: string;
      max_hops?: number;
      edge_types?: EdgeType[];
    }
  • Structured output type for the memory_path tool result.
    export type MemoryPathResult = {
      found: boolean;
      hops: number;
      path: Array<{ id: string; title: string; edge_type_to_next?: EdgeType }>;
      message?: string;
    };
  • src/index.ts:778-814 (registration)
    Registration of the 'memory_path' tool with the MCP server, including input/output schemas and invocation handler.
    server.registerTool(
      "memory_path",
      {
        title: "Shortest path between two memories",
        description: [
          "BFS shortest path between two memories — returns the chain of ids + edge types, or a `no path` message when unreachable within `max_hops`. Read-only.",
          "Use after `memory_search` (with both endpoints known) to trace cause-effect chains, supersession history, or dependency lineage.",
        ].join(" "),
        inputSchema: {
          from_id: z.string().min(1).describe("Starting memory id."),
          to_id: z.string().min(1).describe("Destination memory id."),
          max_hops: z.number().int().min(1).max(10).default(4).describe("Maximum BFS depth (1-10). Default 4. Returns `no path` if the destination is further than `max_hops` from the start."),
          edge_types: z.array(edgeTypeEnum).optional().describe("Optional whitelist of edge types to follow. Omit to allow all types."),
        },
        annotations: {
          title: "Shortest path between two memories",
          readOnlyHint: true,
          destructiveHint: false,
          idempotentHint: true,
          openWorldHint: false,
        },
        outputSchema: {
          found: z.boolean().describe("`true` when a path exists within `max_hops`; `false` otherwise (in which case `path` is empty and `message` carries the reason)."),
          hops: z.number().int().nonnegative().describe("Number of edges in the path. 0 when `from_id === to_id`."),
          path: z.array(z.object({
            id: z.string().describe("Memory id of this node along the path."),
            title: z.string().describe("Memory title (or id when the memory has been deleted)."),
            edge_type_to_next: edgeTypeEnum.optional().describe("The edge type linking this node to the next one in the path. Absent on the final node."),
          })).describe("Ordered list of nodes from `from_id` to `to_id`, each annotated with the edge type that takes you to the next node."),
          message: z.string().optional().describe("Human-readable failure reason when `found=false` (e.g. `No path from <a> to <b> within <n> hops`)."),
        },
      },
      async (params) => {
        const { text, structured } = await findMemoryPath(memRepo, edgesRepo, params);
        return { content: [{ type: "text" as const, text }], structuredContent: structured };
      }
    );
  • BFS shortest path algorithm in EdgesRepo that returns array of node IDs or null if no path within maxHops.
    shortestPath(
      fromId: string,
      toId: string,
      maxHops: number,
      types?: EdgeType[]
    ): string[] | null {
      if (fromId === toId) return [fromId];
    
      const visited = new Map<string, string | null>();
      visited.set(fromId, null);
      let frontier = [fromId];
    
      for (let hop = 0; hop < maxHops; hop++) {
        const nextFrontier: string[] = [];
        for (const nodeId of frontier) {
          const outEdges = this.outgoing(nodeId, types);
          const inEdges = this.incoming(nodeId, types);
          const neighbors = [
            ...outEdges.map(e => e.to_id),
            ...inEdges.map(e => e.from_id),
          ];
          for (const neighbor of neighbors) {
            if (!visited.has(neighbor)) {
              visited.set(neighbor, nodeId);
              if (neighbor === toId) {
                // Reconstruct path
                const path: string[] = [];
                let cur: string | null = toId;
                while (cur !== null) {
                  path.unshift(cur);
                  cur = visited.get(cur) ?? null;
                }
                return path;
              }
              nextFrontier.push(neighbor);
            }
          }
        }
        frontier = nextFrontier;
        if (frontier.length === 0) break;
      }
    
      return null;
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, destructiveHint=false, idempotentHint=true. Description adds that it is read-only, returns 'no path' on unreachable, and uses BFS. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no extraneous text. First sentence defines functionality, second provides usage guidance and purpose. Information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers algorithm, input/output behavior, usage context, and safety. With output schema present, return details are not needed. Lacks performance notes but is sufficient for typical use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%. Description adds extra context for max_hops (returns 'no path' if beyond depth) and edge_types (optional whitelist). for id fields, schema descriptions are sufficient.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the tool performs BFS shortest path between two memories, returning chain of ids and edge types or a 'no path' message. It distinguishes from siblings like memory_search or memory_graph by specifying its use for path tracing and not for general search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Specifies to use after memory_search with both endpoints known, and outlines use cases (cause-effect chains, supersession history, dependency lineage). Does not explicitly state when not to use, but context from siblings is available.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/lfrmonteiro99/memento-mcp'

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