@henrychong-ai/mcp-neo4j-knowledge-graph 2.7.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +86 -0
  2. package/dist/KnowledgeGraphManager.d.ts +40 -0
  3. package/dist/KnowledgeGraphManager.js +101 -0
  4. package/dist/KnowledgeGraphManager.js.map +1 -1
  5. package/dist/cli/report-oversized.d.ts +20 -0
  6. package/dist/cli/report-oversized.js +158 -0
  7. package/dist/cli/report-oversized.js.map +1 -0
  8. package/dist/config/entitySize.d.ts +44 -0
  9. package/dist/config/entitySize.js +77 -0
  10. package/dist/config/entitySize.js.map +1 -0
  11. package/dist/maintenance/EntitySizeService.d.ts +103 -0
  12. package/dist/maintenance/EntitySizeService.js +143 -0
  13. package/dist/maintenance/EntitySizeService.js.map +1 -0
  14. package/dist/server/handlers/callToolHandler.js +22 -1
  15. package/dist/server/handlers/callToolHandler.js.map +1 -1
  16. package/dist/server/handlers/listToolsHandler.js +21 -0
  17. package/dist/server/handlers/listToolsHandler.js.map +1 -1
  18. package/dist/server/handlers/toolHandlers/addObservationsBatch.js +5 -1
  19. package/dist/server/handlers/toolHandlers/addObservationsBatch.js.map +1 -1
  20. package/dist/server/handlers/toolHandlers/createEntitiesBatch.js +5 -1
  21. package/dist/server/handlers/toolHandlers/createEntitiesBatch.js.map +1 -1
  22. package/dist/server/handlers/toolHandlers/updateEntitiesBatch.js +5 -1
  23. package/dist/server/handlers/toolHandlers/updateEntitiesBatch.js.map +1 -1
  24. package/dist/server/handlers/toolHandlers/writeSizeWarnings.d.ts +45 -0
  25. package/dist/server/handlers/toolHandlers/writeSizeWarnings.js +106 -0
  26. package/dist/server/handlers/toolHandlers/writeSizeWarnings.js.map +1 -0
  27. package/dist/storage/StorageProvider.d.ts +27 -0
  28. package/dist/storage/StorageProvider.js.map +1 -1
  29. package/dist/storage/neo4j/Neo4jStorageProvider.d.ts +14 -1
  30. package/dist/storage/neo4j/Neo4jStorageProvider.js +70 -0
  31. package/dist/storage/neo4j/Neo4jStorageProvider.js.map +1 -1
  32. package/example.env +14 -1
  33. package/package.json +6 -3
@@ -4,6 +4,7 @@
4
4
  * Updates multiple entities in a single optimized batch operation.
5
5
  * Provides 10-50x performance improvement over individual updates.
6
6
  */
7
+ import { attachWriteWarnings, collectWriteSizeWarnings, extractWrittenNames, } from './writeSizeWarnings.js';
7
8
  /**
8
9
  * Handle update_entities_batch tool calls
9
10
  *
@@ -15,11 +16,14 @@ export async function handleUpdateEntitiesBatch(args,
15
16
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
17
  knowledgeGraphManager) {
17
18
  const result = await knowledgeGraphManager.updateEntitiesBatch(args.updates, args.config);
19
+ // Additive, fail-open: flag any entity this write pushed near the open_nodes cap.
20
+ const warnings = await collectWriteSizeWarnings(knowledgeGraphManager, extractWrittenNames(args));
21
+ const payload = attachWriteWarnings(result, warnings);
18
22
  return {
19
23
  content: [
20
24
  {
21
25
  type: 'text',
22
- text: JSON.stringify(result, null, 2),
26
+ text: JSON.stringify(payload, null, 2),
23
27
  },
24
28
  ],
25
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"updateEntitiesBatch.js","sourceRoot":"","sources":["../../../../src/server/handlers/toolHandlers/updateEntitiesBatch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,IAA6B;AAC7B,8DAA8D;AAC9D,qBAA0B;IAE1B,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAE1F,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;aACtC;SACF;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"updateEntitiesBatch.js","sourceRoot":"","sources":["../../../../src/server/handlers/toolHandlers/updateEntitiesBatch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,IAA6B;AAC7B,8DAA8D;AAC9D,qBAA0B;IAE1B,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAE1F,kFAAkF;IAClF,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC,qBAAqB,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;IAClG,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEtD,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;aACvC;SACF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared helper: non-fatal entity-size warnings for write tools.
3
+ *
4
+ * After a batch write, the touched entities are sized with EntitySizeService and
5
+ * any that cross the WARN/CRITICAL thresholds are returned as an additive
6
+ * `warnings[]` field on the tool result. This is the earliest possible signal —
7
+ * it names the offending entity at the moment it grows.
8
+ *
9
+ * STRICTLY fail-open: a disabled feature, a sizing error, or a hydration failure
10
+ * yields an empty list and never disrupts or fails the write.
11
+ */
12
+ /** A single non-fatal size warning for a written entity. */
13
+ export interface WriteSizeWarning {
14
+ name: string;
15
+ estTokens: number;
16
+ ratio: number;
17
+ state: 'WARN' | 'CRITICAL';
18
+ message: string;
19
+ }
20
+ /**
21
+ * Extract candidate entity names from a write tool's arguments. Handles the
22
+ * three write shapes: add_observations_batch (observations[].entityName),
23
+ * update_entities_batch (updates[].name), create_entities_batch (entities[].name).
24
+ *
25
+ * @param args Tool arguments
26
+ * @returns Candidate entity names (possibly with duplicates)
27
+ */
28
+ export declare function extractWrittenNames(args: Record<string, unknown>): string[];
29
+ /**
30
+ * Compute non-fatal size warnings for the entities just written.
31
+ *
32
+ * @param knowledgeGraphManager Knowledge graph manager instance
33
+ * @param names Names of the entities that were written
34
+ * @returns WARN/CRITICAL warnings (empty when disabled or on any error)
35
+ */
36
+ export declare function collectWriteSizeWarnings(knowledgeGraphManager: any, names: string[]): Promise<WriteSizeWarning[]>;
37
+ /**
38
+ * Attach size warnings to a write result without mutating existing fields.
39
+ * Returns the original result unchanged when there are no warnings.
40
+ *
41
+ * @param result The batch write result
42
+ * @param warnings Size warnings to attach
43
+ * @returns The result, with an additive `warnings` field when applicable
44
+ */
45
+ export declare function attachWriteWarnings(result: unknown, warnings: WriteSizeWarning[]): unknown;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Shared helper: non-fatal entity-size warnings for write tools.
3
+ *
4
+ * After a batch write, the touched entities are sized with EntitySizeService and
5
+ * any that cross the WARN/CRITICAL thresholds are returned as an additive
6
+ * `warnings[]` field on the tool result. This is the earliest possible signal —
7
+ * it names the offending entity at the moment it grows.
8
+ *
9
+ * STRICTLY fail-open: a disabled feature, a sizing error, or a hydration failure
10
+ * yields an empty list and never disrupts or fails the write.
11
+ */
12
+ import { getEntitySizeConfig } from '../../../config/entitySize.js';
13
+ import { estimateEntitySize } from '../../../maintenance/EntitySizeService.js';
14
+ import { logger } from '../../../utils/logger.js';
15
+ /**
16
+ * Extract candidate entity names from a write tool's arguments. Handles the
17
+ * three write shapes: add_observations_batch (observations[].entityName),
18
+ * update_entities_batch (updates[].name), create_entities_batch (entities[].name).
19
+ *
20
+ * @param args Tool arguments
21
+ * @returns Candidate entity names (possibly with duplicates)
22
+ */
23
+ export function extractWrittenNames(args) {
24
+ const names = [];
25
+ const push = (item) => {
26
+ if (!item || typeof item !== 'object') {
27
+ return;
28
+ }
29
+ const record = item;
30
+ if (typeof record.name === 'string') {
31
+ names.push(record.name);
32
+ }
33
+ if (typeof record.entityName === 'string') {
34
+ names.push(record.entityName);
35
+ }
36
+ };
37
+ for (const key of ['observations', 'updates', 'entities']) {
38
+ const arr = args[key];
39
+ if (Array.isArray(arr)) {
40
+ for (const item of arr) {
41
+ push(item);
42
+ }
43
+ }
44
+ }
45
+ return names;
46
+ }
47
+ /**
48
+ * Compute non-fatal size warnings for the entities just written.
49
+ *
50
+ * @param knowledgeGraphManager Knowledge graph manager instance
51
+ * @param names Names of the entities that were written
52
+ * @returns WARN/CRITICAL warnings (empty when disabled or on any error)
53
+ */
54
+ export async function collectWriteSizeWarnings(
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
+ knowledgeGraphManager, names) {
57
+ try {
58
+ const cfg = getEntitySizeConfig();
59
+ if (!cfg.warnOnWrite) {
60
+ return [];
61
+ }
62
+ const unique = [...new Set(names.filter(n => typeof n === 'string' && n.length > 0))];
63
+ if (unique.length === 0) {
64
+ return [];
65
+ }
66
+ const graph = await knowledgeGraphManager.openNodes(unique);
67
+ const entities = graph?.entities ?? [];
68
+ const warnings = [];
69
+ for (const entity of entities) {
70
+ const report = estimateEntitySize(entity, cfg);
71
+ if (report.state === 'OK') {
72
+ continue;
73
+ }
74
+ const pct = Math.round(report.ratio * 100);
75
+ warnings.push({
76
+ name: report.name,
77
+ estTokens: report.estTokens,
78
+ ratio: Number(report.ratio.toFixed(3)),
79
+ state: report.state,
80
+ message: report.state === 'CRITICAL'
81
+ ? `Entity "${report.name}" is ~${report.estTokens} tokens, at/above the ~${cfg.maxTokens}-token open_nodes cap — split it into sibling entities now; it may already be unretrievable whole.`
82
+ : `Entity "${report.name}" is ~${report.estTokens} tokens (${pct}% of the ~${cfg.maxTokens}-token open_nodes cap) — consider splitting it soon.`,
83
+ });
84
+ }
85
+ return warnings;
86
+ }
87
+ catch (error) {
88
+ logger.warn('collectWriteSizeWarnings failed (non-fatal)', error);
89
+ return [];
90
+ }
91
+ }
92
+ /**
93
+ * Attach size warnings to a write result without mutating existing fields.
94
+ * Returns the original result unchanged when there are no warnings.
95
+ *
96
+ * @param result The batch write result
97
+ * @param warnings Size warnings to attach
98
+ * @returns The result, with an additive `warnings` field when applicable
99
+ */
100
+ export function attachWriteWarnings(result, warnings) {
101
+ if (warnings.length === 0 || !result || typeof result !== 'object') {
102
+ return result;
103
+ }
104
+ return { ...result, warnings };
105
+ }
106
+ //# sourceMappingURL=writeSizeWarnings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeSizeWarnings.js","sourceRoot":"","sources":["../../../../src/server/handlers/toolHandlers/writeSizeWarnings.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,23 @@ export interface SearchOptions {
28
28
  */
29
29
  includeNullDomain?: boolean;
30
30
  }
31
+ /**
32
+ * Compact, cap-safe projection of one entity's approximate serialized size.
33
+ * Returned by {@link StorageProvider.scanEntitySizes}; never carries full
34
+ * observation text, so the scan itself can never breach the MCP output cap.
35
+ */
36
+ export interface EntitySizeScanRow {
37
+ name: string;
38
+ entityType: string;
39
+ /** Approximate serialized characters (observations + structural overhead + relations term). */
40
+ approxChars: number;
41
+ /** Approximate characters contributed by observations alone. */
42
+ obsChars: number;
43
+ /** Number of observations on the entity. */
44
+ obsCount: number;
45
+ /** Number of current relations attached to the entity. */
46
+ relCount: number;
47
+ }
31
48
  /**
32
49
  * Interface for storage providers that can load and save knowledge graphs
33
50
  */
@@ -56,6 +73,16 @@ export interface StorageProvider {
56
73
  * @returns Promise resolving to a KnowledgeGraph containing the specified nodes
57
74
  */
58
75
  openNodes(names: string[]): Promise<KnowledgeGraph>;
76
+ /**
77
+ * Scan current entities ranked by approximate serialized size, largest first.
78
+ * Computes size in the storage layer and returns only a compact projection
79
+ * (never full entities), so the scan is immune to the MCP output cap it
80
+ * polices. Optional — providers that cannot scan efficiently may omit it, and
81
+ * callers fall back to an in-memory pass.
82
+ * @param limit Maximum number of (largest) entities to return
83
+ * @returns Promise resolving to ranked size rows, largest first
84
+ */
85
+ scanEntitySizes?(limit: number): Promise<EntitySizeScanRow[]>;
59
86
  /**
60
87
  * Create new entities in the knowledge graph
61
88
  * @param entities Array of entities to create
@@ -1 +1 @@
1
- {"version":3,"file":"StorageProvider.js","sourceRoot":"","sources":["../../src/storage/StorageProvider.ts"],"names":[],"mappings":"AAqPA,2EAA2E;AAC3E,6EAA6E;AAC7E,2DAA2D;AAC3D,MAAM,KAAW,eAAe,CAK/B;AALD,WAAiB,eAAe;IAC9B,8DAA8D;IAC9D,SAAgB,iBAAiB,CAAC,GAAQ;QACxC,OAAO,wBAAwB,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IAFe,iCAAiB,oBAEhC,CAAA;AACH,CAAC,EALgB,eAAe,KAAf,eAAe,QAK/B;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kFAAkF;IAClF,qDAAqD;IACrD,8DAA8D;IAC9D,iBAAiB,CAAC,GAAQ;QACxB,MAAM,kBAAkB,GACtB,GAAG;YACH,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;YACnC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;YACnC,OAAO,GAAG,CAAC,WAAW,KAAK,UAAU;YACrC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;YACnC,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU;YACxC,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU;YACzC,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU;YACzC,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU;YACxC,OAAO,GAAG,CAAC,kBAAkB,KAAK,UAAU;YAC5C,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU;YACzC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU,CAAC;QAEtC,6DAA6D;QAC7D,MAAM,oBAAoB,GACxB,CAAC,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,UAAU,CAAC;YAC3D,CAAC,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,CAAC;YACjE,CAAC,CAAC,GAAG,CAAC,gBAAgB,IAAI,OAAO,GAAG,CAAC,gBAAgB,KAAK,UAAU,CAAC;YACrE,CAAC,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,GAAG,CAAC,kBAAkB,KAAK,UAAU,CAAC;YACzE,CAAC,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,CAAC;YACjE,CAAC,CAAC,GAAG,CAAC,eAAe,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU,CAAC;YACnE,CAAC,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,GAAG,CAAC,qBAAqB,KAAK,UAAU,CAAC;YAC/E,CAAC,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,GAAG,CAAC,mBAAmB,KAAK,UAAU,CAAC;YAC3E,CAAC,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,CAAC,CAAC;QAEpE,OAAO,kBAAkB,IAAI,oBAAoB,CAAC;IACpD,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"StorageProvider.js","sourceRoot":"","sources":["../../src/storage/StorageProvider.ts"],"names":[],"mappings":"AAkRA,2EAA2E;AAC3E,6EAA6E;AAC7E,2DAA2D;AAC3D,MAAM,KAAW,eAAe,CAK/B;AALD,WAAiB,eAAe;IAC9B,8DAA8D;IAC9D,SAAgB,iBAAiB,CAAC,GAAQ;QACxC,OAAO,wBAAwB,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IAFe,iCAAiB,oBAEhC,CAAA;AACH,CAAC,EALgB,eAAe,KAAf,eAAe,QAK/B;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kFAAkF;IAClF,qDAAqD;IACrD,8DAA8D;IAC9D,iBAAiB,CAAC,GAAQ;QACxB,MAAM,kBAAkB,GACtB,GAAG;YACH,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;YACnC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;YACnC,OAAO,GAAG,CAAC,WAAW,KAAK,UAAU;YACrC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;YACnC,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU;YACxC,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU;YACzC,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU;YACzC,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU;YACxC,OAAO,GAAG,CAAC,kBAAkB,KAAK,UAAU;YAC5C,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU;YACzC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU,CAAC;QAEtC,6DAA6D;QAC7D,MAAM,oBAAoB,GACxB,CAAC,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,UAAU,CAAC;YAC3D,CAAC,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,CAAC;YACjE,CAAC,CAAC,GAAG,CAAC,gBAAgB,IAAI,OAAO,GAAG,CAAC,gBAAgB,KAAK,UAAU,CAAC;YACrE,CAAC,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,GAAG,CAAC,kBAAkB,KAAK,UAAU,CAAC;YACzE,CAAC,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,CAAC;YACjE,CAAC,CAAC,GAAG,CAAC,eAAe,IAAI,OAAO,GAAG,CAAC,eAAe,KAAK,UAAU,CAAC;YACnE,CAAC,CAAC,GAAG,CAAC,qBAAqB,IAAI,OAAO,GAAG,CAAC,qBAAqB,KAAK,UAAU,CAAC;YAC/E,CAAC,CAAC,GAAG,CAAC,mBAAmB,IAAI,OAAO,GAAG,CAAC,mBAAmB,KAAK,UAAU,CAAC;YAC3E,CAAC,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,GAAG,CAAC,cAAc,KAAK,UAAU,CAAC,CAAC;QAEpE,OAAO,kBAAkB,IAAI,oBAAoB,CAAC;IACpD,CAAC;CACF,CAAC"}
@@ -3,7 +3,7 @@ import { type HybridSearchConfig } from '../../retrieval/index.js';
3
3
  import type { BatchConfig, BatchResult, ObservationBatch, EntityUpdate } from '../../types/batch-operations.js';
4
4
  import type { EntityEmbedding, SemanticSearchOptions } from '../../types/entity-embedding.js';
5
5
  import type { Relation } from '../../types/relation.js';
6
- import type { StorageProvider, SearchOptions } from '../StorageProvider.js';
6
+ import type { StorageProvider, SearchOptions, EntitySizeScanRow } from '../StorageProvider.js';
7
7
  import { type Neo4jConfig } from './Neo4jConfig.js';
8
8
  import { Neo4jConnectionManager } from './Neo4jConnectionManager.js';
9
9
  /**
@@ -141,6 +141,19 @@ export declare class Neo4jStorageProvider implements StorageProvider {
141
141
  * @param names Array of node names to open
142
142
  */
143
143
  openNodes(names: string[]): Promise<KnowledgeGraph>;
144
+ /**
145
+ * Scan current entities ranked by approximate serialized size, largest first.
146
+ *
147
+ * Size is computed entirely in Cypher and only a compact projection is
148
+ * returned (name, type, char/observation/relation counts) — never full
149
+ * entities — so this scan can never itself breach the MCP output cap it
150
+ * exists to police. Observations may be stored as a JSON string or a list;
151
+ * both forms are handled via valueType() (Neo4j 5.13+).
152
+ *
153
+ * @param limit Maximum number of (largest) entities to return
154
+ * @returns Ranked size rows, largest approxChars first
155
+ */
156
+ scanEntitySizes(limit: number): Promise<EntitySizeScanRow[]>;
144
157
  /**
145
158
  * Create new entities in the knowledge graph
146
159
  * @param entities Array of entities to create
@@ -596,6 +596,76 @@ export class Neo4jStorageProvider {
596
596
  throw error;
597
597
  }
598
598
  }
599
+ /**
600
+ * Scan current entities ranked by approximate serialized size, largest first.
601
+ *
602
+ * Size is computed entirely in Cypher and only a compact projection is
603
+ * returned (name, type, char/observation/relation counts) — never full
604
+ * entities — so this scan can never itself breach the MCP output cap it
605
+ * exists to police. Observations may be stored as a JSON string or a list;
606
+ * both forms are handled via valueType() (Neo4j 5.13+).
607
+ *
608
+ * @param limit Maximum number of (largest) entities to return
609
+ * @returns Ranked size rows, largest approxChars first
610
+ */
611
+ async scanEntitySizes(limit) {
612
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 50;
613
+ // obsChars: characters contributed by observations, deliberately biased HIGH
614
+ // so a many-short-observation entity (whose real open_nodes JSON carries
615
+ // large per-line indentation overhead) is not ranked below the top-N and
616
+ // missed. String form => its JSON length + ~10 chars/element for the
617
+ // pretty-print indentation the raw string omits; list form => sum of
618
+ // element lengths + ~12 chars/element (8-space indent + quotes + comma).
619
+ // coalesce guards a null element (which would null the whole rank, and
620
+ // Neo4j sorts nulls FIRST, spuriously promoting it). obsCount is the
621
+ // element count for both forms (string form approximated via split).
622
+ // approxChars: obsChars + a fixed structural/metadata overhead + a small
623
+ // per-relation term. Only used for RANKING; precise sizing is refined
624
+ // against the real entity for the returned top-N.
625
+ const query = `
626
+ MATCH (e:Entity)
627
+ WHERE e.validTo IS NULL
628
+ WITH e,
629
+ CASE
630
+ WHEN e.observations IS NULL THEN 0
631
+ WHEN valueType(e.observations) STARTS WITH 'STRING'
632
+ THEN size(e.observations) + (size(split(e.observations, '","')) * 10)
633
+ WHEN valueType(e.observations) STARTS WITH 'LIST'
634
+ THEN reduce(s = 0, o IN e.observations | s + size(coalesce(toString(o), '')) + 12)
635
+ ELSE 0
636
+ END AS obsChars,
637
+ CASE
638
+ WHEN e.observations IS NULL THEN 0
639
+ WHEN valueType(e.observations) STARTS WITH 'LIST' THEN size(e.observations)
640
+ WHEN valueType(e.observations) STARTS WITH 'STRING' THEN size(split(e.observations, '","'))
641
+ ELSE 0
642
+ END AS obsCount
643
+ OPTIONAL MATCH (e)-[r:RELATES_TO]-(:Entity)
644
+ WHERE r.validTo IS NULL
645
+ WITH e, obsChars, obsCount, count(DISTINCT r) AS relCount
646
+ RETURN
647
+ e.name AS name,
648
+ e.entityType AS entityType,
649
+ obsChars AS obsChars,
650
+ obsCount AS obsCount,
651
+ relCount AS relCount,
652
+ (obsChars + 200 + relCount * 8) AS approxChars
653
+ ORDER BY approxChars DESC
654
+ LIMIT toInteger($limit)
655
+ `;
656
+ const result = await this.connectionManager.executeQuery(query, { limit: safeLimit });
657
+ return result.records.map(record => {
658
+ const toNum = (value) => Number(this.convertNeo4jInt(value) ?? 0);
659
+ return {
660
+ name: record.get('name'),
661
+ entityType: record.get('entityType') ?? '',
662
+ approxChars: toNum(record.get('approxChars')),
663
+ obsChars: toNum(record.get('obsChars')),
664
+ obsCount: toNum(record.get('obsCount')),
665
+ relCount: toNum(record.get('relCount')),
666
+ };
667
+ });
668
+ }
599
669
  /**
600
670
  * Create new entities in the knowledge graph
601
671
  * @param entities Array of entities to create