Skip to main content
Glama

Graph Entities

graph_entities
Read-only

Search or browse the entity catalog to check existence before creating, or list entities by type. Returns sorted results up to a specified limit.

Instructions

Browse or search the entity catalog. Use to check if an entity exists before creating one with graph_relate, or to list entities of a given type. For relationship-aware lookups (entity + its neighbors) use graph_query instead. Returns up to limit entities ordered by sort_by; pagination is single-page (raise limit if you need more).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
searchNoFull-text search query
typeNoFilter by entity type (Person, Project, Concept, etc.)
min_confidenceNoMin confidence threshold
sort_byNoSort order (default: confidence)confidence
limitNoMax results (default: 20)

Implementation Reference

  • Registration of the graph_entities tool on the MCP server with input schema and handler. Delegates to client.searchEntities().
    server.registerTool("graph_entities", {
      title: "Graph Entities",
      description:
        "Browse or search the entity catalog. Use to check if an entity exists before creating one with graph_relate, or to list entities of a given type. For relationship-aware lookups (entity + its neighbors) use graph_query instead. Returns up to `limit` entities ordered by `sort_by`; pagination is single-page (raise `limit` if you need more).",
      inputSchema: {
        search: z.string().optional().describe("Full-text search query"),
        type: z.string().optional().describe("Filter by entity type (Person, Project, Concept, etc.)"),
        min_confidence: z.number().optional().describe("Min confidence threshold"),
        sort_by: z.enum(["confidence", "last_seen", "name"]).optional().default("confidence").describe("Sort order (default: confidence)"),
        limit: z.number().optional().default(20).describe("Max results (default: 20)"),
      },
      annotations: { readOnlyHint: true },
    }, async (args) => {
      try {
        const result = await client.searchEntities(currentTenant(), {
          search: args.search,
          type: args.type as EntityType | undefined,
          min_confidence: args.min_confidence,
          sort_by: args.sort_by,
          limit: args.limit,
        });
        return toolResult(result);
      } catch (err) {
        return toolError(`graph_entities failed: ${err instanceof Error ? err.message : String(err)}`);
      }
    });
  • Input schema for graph_entities: search, type, min_confidence, sort_by, limit.
    inputSchema: {
      search: z.string().optional().describe("Full-text search query"),
      type: z.string().optional().describe("Filter by entity type (Person, Project, Concept, etc.)"),
      min_confidence: z.number().optional().describe("Min confidence threshold"),
      sort_by: z.enum(["confidence", "last_seen", "name"]).optional().default("confidence").describe("Sort order (default: confidence)"),
      limit: z.number().optional().default(20).describe("Max results (default: 20)"),
    },
  • Handler function for graph_entities - delegates to client.searchEntities() with the provided filters.
    }, async (args) => {
      try {
        const result = await client.searchEntities(currentTenant(), {
          search: args.search,
          type: args.type as EntityType | undefined,
          min_confidence: args.min_confidence,
          sort_by: args.sort_by,
          limit: args.limit,
        });
        return toolResult(result);
      } catch (err) {
        return toolError(`graph_entities failed: ${err instanceof Error ? err.message : String(err)}`);
      }
    });
  • The core implementation in Neo4jClient.searchEntities() that executes Cypher queries to search/filter/sort entities by full-text search or direct filtering, with edge count and total.
    async searchEntities(
      tenantId: string,
      options: {
        search?: string;
        type?: EntityType;
        min_confidence?: number;
        sort_by?: "confidence" | "last_seen" | "name";
        limit?: number;
      } = {},
    ): Promise<{ entities: EntityNode[]; total: number }> {
      const limit = options.limit ?? 20;
      let cypher: string;
      let params: Record<string, unknown>;
    
      if (options.search) {
        const typeFilter = options.type ? `AND ANY(l IN labels(node) WHERE l = $type)` : "";
        const confFilter = options.min_confidence != null ? `AND node.confidence >= $minConf` : "";
        cypher = `
          CALL db.index.fulltext.queryNodes('entity_names', $search)
          YIELD node, score
          WHERE node:Entity AND node.tenant_id = $tenantId ${typeFilter} ${confFilter}
          WITH node, score
          OPTIONAL MATCH (node)-[r]-(other:Entity {tenant_id: $tenantId})
          WITH node, labels(node) AS labels, count(r) AS edge_count, score
          RETURN node, labels, edge_count
          ORDER BY score DESC
          LIMIT $limit
        `;
        params = {
          tenantId,
          search: options.search,
          limit,
          ...(options.type ? { type: options.type } : {}),
          ...(options.min_confidence != null ? { minConf: options.min_confidence } : {}),
        };
      } else {
        const typeMatch = options.type ? `(n:\`${options.type}\` {tenant_id: $tenantId})` : "(n:Entity {tenant_id: $tenantId})";
        const confFilter = options.min_confidence != null ? `WHERE n.confidence >= $minConf` : "";
        const orderBy =
          options.sort_by === "last_seen"
            ? "n.last_seen DESC"
            : options.sort_by === "name"
              ? "n.name ASC"
              : "n.confidence DESC";
    
        cypher = `
          MATCH ${typeMatch}
          ${confFilter}
          OPTIONAL MATCH (n)-[r]-(other:Entity {tenant_id: $tenantId})
          WITH n, labels(n) AS labels, count(r) AS edge_count
          RETURN n AS node, labels, edge_count
          ORDER BY ${orderBy}
          LIMIT $limit
        `;
        params = {
          tenantId,
          limit,
          ...(options.min_confidence != null ? { minConf: options.min_confidence } : {}),
        };
      }
    
      const rows = await this.run(cypher, params);
    
      const entities = rows.map((row) => {
        const nodeObj = row["node"] as { labels: string[]; properties: Record<string, unknown> };
        const entity = recordToEntity(nodeObj.properties, nodeObj.labels);
        (entity as EntityNode & { edge_count?: number }).edge_count = Number(row["edge_count"] ?? 0);
        return entity;
      });
    
      // Tenant-scoped total count
      const countCypher = options.type
        ? `MATCH (n:\`${options.type}\` {tenant_id: $tenantId}) RETURN count(n) AS total`
        : `MATCH (n:Entity {tenant_id: $tenantId}) RETURN count(n) AS total`;
      const countRows = await this.run(countCypher, { tenantId });
      const totalNum = Number(countRows[0]?.["total"] ?? 0);
    
      return { entities, total: totalNum };
    }
Behavior4/5

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

Annotations mark readOnlyHint=true. The description adds beyond this: 'Returns up to `limit` entities ordered by `sort_by`; pagination is single-page (raise `limit` if you need more).' This discloses return behavior and pagination constraint. No contradiction with annotations.

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?

Three sentences, each purposeful. First sentence states purpose, second gives use cases and alternatives, third explains return behavior and pagination. No wasted words.

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?

No output schema, but description covers return behavior (up to limit entities, ordered) and pagination. Provides enough context for an agent to understand what the tool returns without needing an output schema. Minor omission: doesn't mention if count or metadata is included.

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%, baseline 3. The description adds meaning by explaining `limit` and `sort_by` behavior ('returns up to limit entities ordered by sort_by') and clarifies `search` as full-text. This slightly exceeds baseline.

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 'Browse or search the entity catalog' and gives specific use cases: checking existence before creation and listing entities by type. It distinguishes from sibling tool graph_query by explicitly stating 'For relationship-aware lookups... use graph_query instead.'

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

Usage Guidelines5/5

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

Provides explicit when-to-use (checking existence before graph_relate, listing entities) and when-not-to-use (relationship-aware lookups) with a named alternative (graph_query). This gives clear guidance for an AI agent.

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