Skip to main content
Glama

twining_prune_graph

Remove disconnected entities from your knowledge graph to clean up stale data. Optionally filter by entity type for targeted pruning.

Instructions

Remove orphaned knowledge graph entities that have no relations. Use this to clean up stale or disconnected entities. Optionally filter by entity type to only prune certain kinds.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
entity_typesNoOnly prune orphans of these types (e.g., ["concept", "file"]). If omitted, prunes all orphan types.
dry_runNoIf true, report orphans without removing them (default: false)

Implementation Reference

  • The GraphEngine.prune() method implements the core logic for the twining_prune_graph tool. It finds orphaned entities (entities with no relations), optionally filters by entity type, and either reports them (dry run) or removes them via GraphStore.removeEntities().
    async prune(entityTypes?: string[], dryRun = false): Promise<{
      pruned: Array<{ id: string; name: string; type: string }>;
      total_orphans_found: number;
      total_removed: number;
      removed_relations: number;
      dry_run: boolean;
    }> {
      const entities = await this.graphStore.getEntities();
      const relations = await this.graphStore.getRelations();
    
      // Find all entity IDs that participate in at least one relation
      const connectedIds = new Set<string>();
      for (const r of relations) {
        connectedIds.add(r.source);
        connectedIds.add(r.target);
      }
    
      // Find orphans
      let orphans = entities.filter((e) => !connectedIds.has(e.id));
      const total_orphans_found = orphans.length;
    
      // Filter by type if requested
      if (entityTypes && entityTypes.length > 0) {
        const typeSet = new Set(entityTypes);
        orphans = orphans.filter((e) => typeSet.has(e.type));
      }
    
      if (orphans.length === 0 || dryRun) {
        return {
          pruned: orphans.map((e) => ({ id: e.id, name: e.name, type: e.type })),
          total_orphans_found,
          total_removed: 0,
          removed_relations: 0,
          dry_run: dryRun,
        };
      }
    
      const orphanIds = new Set(orphans.map((e) => e.id));
      const { removedEntities, removedRelations } =
        await this.graphStore.removeEntities(orphanIds);
    
      return {
        pruned: orphans.map((e) => ({ id: e.id, name: e.name, type: e.type })),
        total_orphans_found,
        total_removed: removedEntities,
        removed_relations: removedRelations,
        dry_run: false,
      };
    }
  • The tool handler for twining_prune_graph registered with the MCP server. Accepts optional entity_types and dry_run parameters, and calls engine.prune().
    // twining_prune_graph — Remove orphaned graph entities
    server.registerTool(
      "twining_prune_graph",
      {
        description:
          "Remove orphaned knowledge graph entities that have no relations. Use this to clean up stale or disconnected entities. Optionally filter by entity type to only prune certain kinds.",
        inputSchema: {
          entity_types: z
            .array(z.string())
            .optional()
            .describe(
              'Only prune orphans of these types (e.g., ["concept", "file"]). If omitted, prunes all orphan types.',
            ),
          dry_run: z
            .boolean()
            .optional()
            .describe(
              "If true, report orphans without removing them (default: false)",
            ),
        },
      },
      async (args) => {
        try {
          const result = await engine.prune(
            args.entity_types,
            args.dry_run ?? false,
          );
          return toolResult(result);
        } catch (e) {
          if (e instanceof TwiningError) {
            return toolError(e.message, e.code);
          }
          return toolError(
            e instanceof Error ? e.message : "Unknown error",
            "INTERNAL_ERROR",
          );
        }
      },
    );
  • Input schema for twining_prune_graph: optional entity_types array and dry_run boolean.
    inputSchema: {
      entity_types: z
        .array(z.string())
        .optional()
        .describe(
          'Only prune orphans of these types (e.g., ["concept", "file"]). If omitted, prunes all orphan types.',
        ),
      dry_run: z
        .boolean()
        .optional()
        .describe(
          "If true, report orphans without removing them (default: false)",
        ),
    },
  • Registration of twining_prune_graph via server.registerTool() inside the registerGraphTools function.
    // twining_prune_graph — Remove orphaned graph entities
    server.registerTool(
      "twining_prune_graph",
      {
        description:
          "Remove orphaned knowledge graph entities that have no relations. Use this to clean up stale or disconnected entities. Optionally filter by entity type to only prune certain kinds.",
        inputSchema: {
          entity_types: z
            .array(z.string())
            .optional()
            .describe(
              'Only prune orphans of these types (e.g., ["concept", "file"]). If omitted, prunes all orphan types.',
            ),
          dry_run: z
            .boolean()
            .optional()
            .describe(
              "If true, report orphans without removing them (default: false)",
            ),
        },
      },
      async (args) => {
        try {
          const result = await engine.prune(
            args.entity_types,
            args.dry_run ?? false,
          );
          return toolResult(result);
        } catch (e) {
          if (e instanceof TwiningError) {
            return toolError(e.message, e.code);
          }
          return toolError(
            e instanceof Error ? e.message : "Unknown error",
            "INTERNAL_ERROR",
          );
        }
      },
    );
  • GraphStore.removeEntities() performs the actual deletion of orphan entities and their relations from storage files with file locking.
    async removeEntities(
      entityIds: Set<string>,
    ): Promise<{ removedEntities: number; removedRelations: number }> {
      this.ensureFiles();
    
      let removedEntities = 0;
      let removedRelations = 0;
    
      // Lock both files — entities first, then relations
      const releaseEntities = await lockfile.lock(
        this.entitiesPath,
        LOCK_OPTIONS,
      );
      try {
        const entities = JSON.parse(
          fs.readFileSync(this.entitiesPath, "utf-8"),
        ) as Entity[];
        const before = entities.length;
        const kept = entities.filter((e) => !entityIds.has(e.id));
        removedEntities = before - kept.length;
        fs.writeFileSync(
          this.entitiesPath,
          JSON.stringify(kept, null, 2),
        );
      } finally {
        await releaseEntities();
      }
    
      const releaseRelations = await lockfile.lock(
        this.relationsPath,
        LOCK_OPTIONS,
      );
      try {
        const relations = JSON.parse(
          fs.readFileSync(this.relationsPath, "utf-8"),
        ) as Relation[];
        const before = relations.length;
        const kept = relations.filter(
          (r) => !entityIds.has(r.source) && !entityIds.has(r.target),
        );
        removedRelations = before - kept.length;
        fs.writeFileSync(
          this.relationsPath,
          JSON.stringify(kept, null, 2),
        );
      } finally {
        await releaseRelations();
      }
    
      return { removedEntities, removedRelations };
    }
Behavior4/5

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

With no annotations, the description carries the full burden. It explains that the tool removes orphans and discloses the dry_run behavior for safe reporting. It does not cover potential side effects, permissions, or what happens if no orphans exist, but for a simple prune operation it is reasonably transparent.

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 and a brief note about optional filtering; every word earns its place. The description is front-loaded with the core purpose and is highly efficient with no redundancy.

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

Completeness3/5

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

No output schema is provided, and the description does not explain what the tool returns (e.g., list of removed IDs, count). It also does not differentiate from sibling tools like twining_archive_stale or twining_housekeeping, which may overlap in functionality.

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 coverage is 100% and the description adds minimal new meaning beyond the schema descriptions. It restates 'only prune orphans of these types' for entity_types and 'report orphans without removing them' for dry_run, which adds slight clarity but does not significantly enrich the schema information.

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 the action ('remove') and the resource ('orphaned knowledge graph entities') with an explicit definition of orphaned (no relations). It distinguishes this tool from sibling operations like twining_add_entity or twining_neighbors by focusing on cleanup of stale, disconnected entries.

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?

It explicitly says 'Use this to clean up stale or disconnected entities' and mentions optional filtering. However, it does not specify when NOT to use (e.g., if you need to archive or delete specific entities) or mention alternatives like twining_archive_stale or twining_housekeeping.

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/daveangulo/twining-mcp'

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