@@ -98,6 +98,12 @@ export class KnowledgeGraphManager {
9898 }
9999 }
100100
101+ private static sliceObservations ( obs : string [ ] , options ?: { limit ?: number ; offset ?: number } ) : string [ ] {
102+ if ( ! options || ( options . offset === undefined && options . limit === undefined ) ) return obs ;
103+ const offset = options . offset ?? 0 ;
104+ return options . limit !== undefined ? obs . slice ( offset , offset + options . limit ) : obs . slice ( offset ) ;
105+ }
106+
101107 private async saveGraph ( graph : KnowledgeGraph ) : Promise < void > {
102108 const lines = [
103109 ...graph . entities . map ( e => JSON . stringify ( {
@@ -179,61 +185,74 @@ export class KnowledgeGraphManager {
179185 await this . saveGraph ( graph ) ;
180186 }
181187
182- async readGraph ( ) : Promise < KnowledgeGraph > {
183- return this . loadGraph ( ) ;
188+ async readGraph ( options ?: {
189+ includeObservations ?: boolean ;
190+ observationLimit ?: number ;
191+ entityTypes ?: string [ ] ;
192+ metadataOnly ?: boolean ;
193+ } ) : Promise < KnowledgeGraph > {
194+ const graph = await this . loadGraph ( ) ;
195+ let entities = graph . entities ;
196+ if ( options ?. entityTypes ?. length ) {
197+ entities = entities . filter ( e => options . entityTypes ! . includes ( e . entityType ) ) ;
198+ }
199+ const withObservations = options ?. metadataOnly || options ?. includeObservations === false ? [ ] : undefined ;
200+ const processedEntities = entities . map ( e => ( {
201+ name : e . name ,
202+ entityType : e . entityType ,
203+ observations : withObservations ?? KnowledgeGraphManager . sliceObservations ( e . observations , { limit : options ?. observationLimit } )
204+ } ) ) ;
205+ const names = new Set ( processedEntities . map ( e => e . name ) ) ;
206+ return {
207+ entities : processedEntities ,
208+ relations : graph . relations . filter ( r => names . has ( r . from ) && names . has ( r . to ) )
209+ } ;
184210 }
185211
186212 // Very basic search function
187- async searchNodes ( query : string ) : Promise < KnowledgeGraph > {
213+ async searchNodes ( query : string , options ?: {
214+ includeObservations ?: boolean ;
215+ limit ?: number ;
216+ observationLimit ?: number ;
217+ } ) : Promise < KnowledgeGraph > {
188218 const graph = await this . loadGraph ( ) ;
189-
190- // Filter entities
191- const filteredEntities = graph . entities . filter ( e =>
219+ let entities = graph . entities . filter ( e =>
192220 e . name . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) ||
193221 e . entityType . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) ||
194222 e . observations . some ( o => o . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) )
195223 ) ;
196-
197- // Create a Set of filtered entity names for quick lookup
198- const filteredEntityNames = new Set ( filteredEntities . map ( e => e . name ) ) ;
199-
200- // Include relations where at least one endpoint matches the search results.
201- // This lets callers discover connections to nodes outside the result set.
202- const filteredRelations = graph . relations . filter ( r =>
203- filteredEntityNames . has ( r . from ) || filteredEntityNames . has ( r . to )
204- ) ;
205-
206- const filteredGraph : KnowledgeGraph = {
207- entities : filteredEntities ,
208- relations : filteredRelations ,
224+ if ( options ?. limit !== undefined ) entities = entities . slice ( 0 , options . limit ) ;
225+ const withObservations = options ?. includeObservations === false ? [ ] : undefined ;
226+ const processedEntities = entities . map ( e => ( {
227+ name : e . name ,
228+ entityType : e . entityType ,
229+ observations : withObservations ?? KnowledgeGraphManager . sliceObservations ( e . observations , { limit : options ?. observationLimit } )
230+ } ) ) ;
231+ const names = new Set ( processedEntities . map ( e => e . name ) ) ;
232+ return {
233+ entities : processedEntities ,
234+ relations : graph . relations . filter ( r => names . has ( r . from ) || names . has ( r . to ) )
209235 } ;
210-
211- return filteredGraph ;
212236 }
213237
214- async openNodes ( names : string [ ] ) : Promise < KnowledgeGraph > {
238+ async openNodes ( names : string [ ] , options ?: {
239+ includeObservations ?: boolean ;
240+ observationLimit ?: number ;
241+ observationOffset ?: number ;
242+ } ) : Promise < KnowledgeGraph > {
215243 const graph = await this . loadGraph ( ) ;
216-
217- // Filter entities
218244 const filteredEntities = graph . entities . filter ( e => names . includes ( e . name ) ) ;
219-
220- // Create a Set of filtered entity names for quick lookup
221- const filteredEntityNames = new Set ( filteredEntities . map ( e => e . name ) ) ;
222-
223- // Include relations where at least one endpoint is in the requested set.
224- // Previously this required BOTH endpoints, which meant relations from a
225- // requested node to an unrequested node were silently dropped — making it
226- // impossible to discover a node's connections without reading the full graph.
227- const filteredRelations = graph . relations . filter ( r =>
228- filteredEntityNames . has ( r . from ) || filteredEntityNames . has ( r . to )
229- ) ;
230-
231- const filteredGraph : KnowledgeGraph = {
232- entities : filteredEntities ,
233- relations : filteredRelations ,
245+ const withObservations = options ?. includeObservations === false ? [ ] : undefined ;
246+ const processedEntities = filteredEntities . map ( e => ( {
247+ name : e . name ,
248+ entityType : e . entityType ,
249+ observations : withObservations ?? KnowledgeGraphManager . sliceObservations ( e . observations , { offset : options ?. observationOffset , limit : options ?. observationLimit } )
250+ } ) ) ;
251+ const entityNames = new Set ( processedEntities . map ( e => e . name ) ) ;
252+ return {
253+ entities : processedEntities ,
254+ relations : graph . relations . filter ( r => entityNames . has ( r . from ) || entityNames . has ( r . to ) )
234255 } ;
235-
236- return filteredGraph ;
237256 }
238257}
239258
@@ -407,19 +426,21 @@ server.registerTool(
407426 "read_graph" ,
408427 {
409428 title : "Read Graph" ,
410- description : "Read the entire knowledge graph" ,
411- inputSchema : { } ,
429+ description : "Read the entire knowledge graph. Optional params: metadataOnly (names/types only), includeObservations (default true), observationLimit (max per entity), entityTypes (filter by types)." ,
430+ inputSchema : {
431+ includeObservations : z . boolean ( ) . optional ( ) . describe ( "Whether to include observation content (default: true)" ) ,
432+ observationLimit : z . number ( ) . int ( ) . positive ( ) . optional ( ) . describe ( "Max observations per entity" ) ,
433+ entityTypes : z . array ( z . string ( ) ) . optional ( ) . describe ( "Filter entities by types" ) ,
434+ metadataOnly : z . boolean ( ) . optional ( ) . describe ( "Return only entity names and types, no observations" )
435+ } ,
412436 outputSchema : {
413437 entities : z . array ( EntitySchema ) ,
414438 relations : z . array ( RelationSchema )
415439 }
416440 } ,
417- async ( ) => {
418- const graph = await knowledgeGraphManager . readGraph ( ) ;
419- return {
420- content : [ { type : "text" as const , text : JSON . stringify ( graph , null , 2 ) } ] ,
421- structuredContent : { ...graph }
422- } ;
441+ async ( args ) => {
442+ const graph = await knowledgeGraphManager . readGraph ( args ) ;
443+ return { content : [ { type : "text" , text : JSON . stringify ( graph , null , 2 ) } ] , structuredContent : { ...graph } } ;
423444 }
424445) ;
425446
@@ -428,21 +449,21 @@ server.registerTool(
428449 "search_nodes" ,
429450 {
430451 title : "Search Nodes" ,
431- description : "Search for nodes in the knowledge graph based on a query " ,
452+ description : "Search for nodes by query. Optional: includeObservations (default true), limit (max entities), observationLimit (max per entity). " ,
432453 inputSchema : {
433- query : z . string ( ) . describe ( "The search query to match against entity names, types, and observation content" )
454+ query : z . string ( ) . describe ( "Search query" ) ,
455+ includeObservations : z . boolean ( ) . optional ( ) . describe ( "Include observations (default: true)" ) ,
456+ limit : z . number ( ) . int ( ) . positive ( ) . optional ( ) . describe ( "Max entities to return" ) ,
457+ observationLimit : z . number ( ) . int ( ) . positive ( ) . optional ( ) . describe ( "Max observations per entity" )
434458 } ,
435459 outputSchema : {
436460 entities : z . array ( EntitySchema ) ,
437461 relations : z . array ( RelationSchema )
438462 }
439463 } ,
440- async ( { query } ) => {
441- const graph = await knowledgeGraphManager . searchNodes ( query ) ;
442- return {
443- content : [ { type : "text" as const , text : JSON . stringify ( graph , null , 2 ) } ] ,
444- structuredContent : { ...graph }
445- } ;
464+ async ( { query, ...options } ) => {
465+ const graph = await knowledgeGraphManager . searchNodes ( query , options ) ;
466+ return { content : [ { type : "text" , text : JSON . stringify ( graph , null , 2 ) } ] , structuredContent : { ...graph } } ;
446467 }
447468) ;
448469
@@ -451,21 +472,21 @@ server.registerTool(
451472 "open_nodes" ,
452473 {
453474 title : "Open Nodes" ,
454- description : "Open specific nodes in the knowledge graph by their names " ,
475+ description : "Open specific nodes by name. Optional: includeObservations (default true), observationLimit, observationOffset (for pagination). " ,
455476 inputSchema : {
456- names : z . array ( z . string ( ) ) . describe ( "An array of entity names to retrieve" )
477+ names : z . array ( z . string ( ) ) . describe ( "Entity names to retrieve" ) ,
478+ includeObservations : z . boolean ( ) . optional ( ) . describe ( "Include observations (default: true)" ) ,
479+ observationLimit : z . number ( ) . int ( ) . positive ( ) . optional ( ) . describe ( "Max observations per entity" ) ,
480+ observationOffset : z . number ( ) . int ( ) . nonnegative ( ) . optional ( ) . describe ( "Observations to skip (for pagination)" )
457481 } ,
458482 outputSchema : {
459483 entities : z . array ( EntitySchema ) ,
460484 relations : z . array ( RelationSchema )
461485 }
462486 } ,
463- async ( { names } ) => {
464- const graph = await knowledgeGraphManager . openNodes ( names ) ;
465- return {
466- content : [ { type : "text" as const , text : JSON . stringify ( graph , null , 2 ) } ] ,
467- structuredContent : { ...graph }
468- } ;
487+ async ( { names, ...options } ) => {
488+ const graph = await knowledgeGraphManager . openNodes ( names , options ) ;
489+ return { content : [ { type : "text" , text : JSON . stringify ( graph , null , 2 ) } ] , structuredContent : { ...graph } } ;
469490 }
470491) ;
471492
0 commit comments