Skip to main content
Glama

Export Graph

graph_export

Export all graph nodes and edges to a timestamped JSONL backup file, automatically pruning old backups to save space. Use before risky operations or on a weekly schedule.

Instructions

Export all graph nodes and edges to a timestamped JSONL backup file in the backups/ directory. Run this before any risky operation, or on a weekly schedule. Old backups are pruned automatically.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
keepNoNumber of backup files to keep (default 14, ~2 weeks of daily backups).
labelNoOptional label appended to the filename, e.g. 'pre-prune' → backup-2026-05-05-pre-prune.jsonl

Implementation Reference

  • Tool registration for graph_export in the MCP server. Defines the tool name 'graph_export', its description, inputSchema, and the handler callback that orchestrates the export.
    // ─── Tool: graph_export ───
    
    server.registerTool("graph_export", {
      title: "Export Graph",
      description:
        "Export all graph nodes and edges to a timestamped JSONL backup file in the backups/ directory. " +
        "Run this before any risky operation, or on a weekly schedule. Old backups are pruned automatically.",
      inputSchema: {
        keep: z
          .number()
          .int()
          .min(1)
          .max(30)
          .optional()
          .default(14)
          .describe("Number of backup files to keep (default 14, ~2 weeks of daily backups)."),
        label: z
          .string()
          .optional()
          .describe("Optional label appended to the filename, e.g. 'pre-prune' → backup-2026-05-05-pre-prune.jsonl"),
      },
    }, async ({ keep = 14, label }) => {
      const backupsDir = join(GRAPH_MEMORY_HOME, "backups");
      mkdirSync(backupsDir, { recursive: true });
    
      const now = new Date();
      const datePart = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
      const suffix = label ? `-${label.replace(/[^a-z0-9-]/gi, "-")}` : "";
      const filename = `backup-${datePart}${suffix}.jsonl`;
      const filePath = join(backupsDir, filename);
    
      try {
        const tenantId = currentTenant();
        const { nodes, edges } = await client.exportGraph(tenantId);
    
        const lines: string[] = [];
        lines.push(JSON.stringify({ record: "meta", exported_at: now.toISOString(), tenant_id: tenantId, node_count: nodes.length, edge_count: edges.length }));
        for (const node of nodes) lines.push(JSON.stringify({ record: "node", ...node }));
        for (const edge of edges) lines.push(JSON.stringify({ record: "edge", ...edge }));
    
        writeFileSync(filePath, lines.join("\n") + "\n");
        const sizeBytes = statSync(filePath).size;
    
        // Prune old backups — keep the N most recent
        const allBackups = readdirSync(backupsDir)
          .filter((f) => f.startsWith("backup-") && f.endsWith(".jsonl"))
          .map((f) => ({ name: f, mtime: statSync(join(backupsDir, f)).mtimeMs }))
          .sort((a, b) => b.mtime - a.mtime);
    
        const toDelete = allBackups.slice(keep);
        for (const f of toDelete) {
          try { unlinkSync(join(backupsDir, f.name)); } catch { /* ignore */ }
        }
    
        return toolResult({
          backup_file: filePath,
          node_count: nodes.length,
          edge_count: edges.length,
          size_bytes: sizeBytes,
          pruned: toDelete.length,
          retained: Math.min(allBackups.length, keep),
        });
      } catch (err) {
        const e = err instanceof Error ? err : new Error(String(err));
        return toolError(`graph_export failed: ${e.message}`);
      }
    });
  • The handler function for graph_export. Exports all graph nodes and edges to a timestamped JSONL backup file in the backups/ directory, then prunes old backups keeping only the N most recent files.
    }, async ({ keep = 14, label }) => {
      const backupsDir = join(GRAPH_MEMORY_HOME, "backups");
      mkdirSync(backupsDir, { recursive: true });
    
      const now = new Date();
      const datePart = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
      const suffix = label ? `-${label.replace(/[^a-z0-9-]/gi, "-")}` : "";
      const filename = `backup-${datePart}${suffix}.jsonl`;
      const filePath = join(backupsDir, filename);
    
      try {
        const tenantId = currentTenant();
        const { nodes, edges } = await client.exportGraph(tenantId);
    
        const lines: string[] = [];
        lines.push(JSON.stringify({ record: "meta", exported_at: now.toISOString(), tenant_id: tenantId, node_count: nodes.length, edge_count: edges.length }));
        for (const node of nodes) lines.push(JSON.stringify({ record: "node", ...node }));
        for (const edge of edges) lines.push(JSON.stringify({ record: "edge", ...edge }));
    
        writeFileSync(filePath, lines.join("\n") + "\n");
        const sizeBytes = statSync(filePath).size;
    
        // Prune old backups — keep the N most recent
        const allBackups = readdirSync(backupsDir)
          .filter((f) => f.startsWith("backup-") && f.endsWith(".jsonl"))
          .map((f) => ({ name: f, mtime: statSync(join(backupsDir, f)).mtimeMs }))
          .sort((a, b) => b.mtime - a.mtime);
    
        const toDelete = allBackups.slice(keep);
        for (const f of toDelete) {
          try { unlinkSync(join(backupsDir, f.name)); } catch { /* ignore */ }
        }
    
        return toolResult({
          backup_file: filePath,
          node_count: nodes.length,
          edge_count: edges.length,
          size_bytes: sizeBytes,
          pruned: toDelete.length,
          retained: Math.min(allBackups.length, keep),
        });
      } catch (err) {
        const e = err instanceof Error ? err : new Error(String(err));
        return toolError(`graph_export failed: ${e.message}`);
      }
    });
  • The Neo4jClient.exportGraph() method that queries Neo4j for all nodes and edges belonging to a tenant and returns them as plain records for serialization into the JSONL backup file.
    async exportGraph(tenantId: string): Promise<{
      nodes: Record<string, unknown>[];
      edges: Record<string, unknown>[];
    }> {
      const nodeRows = await this.run(`
        MATCH (n:Entity {tenant_id: $tenantId})
        RETURN n.id AS id,
               [l IN labels(n) WHERE l <> 'Entity'][0] AS type,
               n.name AS name,
               n.subtype AS subtype,
               n.confidence AS confidence,
               n.times_mentioned AS times_mentioned,
               n.first_seen AS first_seen,
               n.last_seen AS last_seen,
               n.source_file AS source_file,
               n.tenant_id AS tenant_id,
               properties(n) AS props
        ORDER BY n.first_seen
      `, { tenantId });
    
      const edgeRows = await this.run(`
        MATCH (a:Entity {tenant_id: $tenantId})-[r]->(b:Entity {tenant_id: $tenantId})
        RETURN a.id AS from_id,
               b.id AS to_id,
               type(r) AS relation,
               r.weight AS weight,
               r.last_confirmed AS last_confirmed,
               r.valid_at AS valid_at,
               r.invalid_at AS invalid_at,
               r.ingested_at AS ingested_at,
               r.tenant_id AS tenant_id,
               r.source_session AS source_session,
               r.source_transcript AS source_transcript,
               r.source_type AS source_type,
               r.evidence AS evidence,
               properties(r) AS props
        ORDER BY a.id, type(r)
      `, { tenantId });
    
      return {
        nodes: nodeRows,
        edges: edgeRows,
      };
    }
  • Input validation schema for graph_export using Zod. Accepts optional 'keep' (number of backups to retain, default 14) and optional 'label' string for identifying the backup.
    inputSchema: {
      keep: z
        .number()
        .int()
        .min(1)
        .max(30)
        .optional()
        .default(14)
        .describe("Number of backup files to keep (default 14, ~2 weeks of daily backups)."),
      label: z
        .string()
        .optional()
        .describe("Optional label appended to the filename, e.g. 'pre-prune' → backup-2026-05-05-pre-prune.jsonl"),
    },
Behavior4/5

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

No annotations are present, so the description carries full responsibility. It discloses that the tool creates a timestamped JSONL file in the backups/ directory and that old backups are pruned automatically. It does not specify side effects on the graph itself, but 'export' implies read-only, which is acceptable.

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?

The description is three sentences long, front-loaded with the core purpose, followed by usage guidance and behavioral detail. Every sentence adds value with no redundancy.

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?

Given no output schema, the description adequately covers what is exported (all nodes and edges), the format (JSONL), and the destination (backups/ directory). It omits explicit mention of return values, but for a backup tool this is generally acceptable.

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

Parameters3/5

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

Schema description coverage is 100%, so baseline is 3. The description adds no extra semantics beyond what the schema already provides for the 'keep' and 'label' parameters. It mentions timestamped filenames, but that is output-related rather than parameter-specific.

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?

The description clearly states 'Export all graph nodes and edges to a timestamped JSONL backup file', specifying the exact verb ('export') and resource ('graph nodes and edges'), which distinguishes it from sibling tools like graph_delete or graph_prune.

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?

The description explicitly recommends running 'before any risky operation, or on a weekly schedule', providing clear usage guidance. While it does not list alternatives, the context is clear enough for an agent to decide when to invoke.

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/stevepridemore/graph-memory'

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