@danielsimonjr/memory-mcp 0.41.0 → 0.48.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.
- package/dist/__tests__/file-path.test.js +6 -2
- package/dist/__tests__/performance/benchmarks.test.js +151 -149
- package/dist/core/KnowledgeGraphManager.js +43 -20
- package/dist/features/AnalyticsManager.js +11 -11
- package/dist/features/CompressionManager.js +1 -20
- package/dist/features/index.js +1 -1
- package/dist/index.js +4 -37
- package/dist/memory-saved-searches.jsonl +0 -0
- package/dist/memory-tag-aliases.jsonl +0 -0
- package/dist/memory.jsonl +23 -222
- package/dist/search/BasicSearch.js +21 -51
- package/dist/search/BooleanSearch.js +9 -30
- package/dist/search/FuzzySearch.js +11 -30
- package/dist/search/RankedSearch.js +4 -20
- package/dist/search/SearchFilterChain.js +187 -0
- package/dist/search/index.js +4 -0
- package/dist/server/MCPServer.js +5 -842
- package/dist/server/toolDefinitions.js +732 -0
- package/dist/server/toolHandlers.js +117 -0
- package/dist/types/import-export.types.js +1 -1
- package/dist/utils/constants.js +3 -2
- package/dist/utils/entityUtils.js +108 -0
- package/dist/utils/filterUtils.js +155 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/paginationUtils.js +81 -0
- package/dist/utils/responseFormatter.js +55 -0
- package/dist/utils/tagUtils.js +107 -0
- package/dist/utils/validationHelper.js +99 -0
- package/package.json +34 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Handlers
|
|
3
|
+
*
|
|
4
|
+
* Extracted from MCPServer.ts to reduce file size and improve maintainability.
|
|
5
|
+
* Contains handler functions for all 45 Knowledge Graph tools.
|
|
6
|
+
*
|
|
7
|
+
* @module server/toolHandlers
|
|
8
|
+
*/
|
|
9
|
+
import { formatToolResponse, formatTextResponse, formatRawResponse } from '../utils/responseFormatter.js';
|
|
10
|
+
/**
|
|
11
|
+
* Registry of all tool handlers keyed by tool name.
|
|
12
|
+
*/
|
|
13
|
+
export const toolHandlers = {
|
|
14
|
+
// ==================== ENTITY HANDLERS ====================
|
|
15
|
+
create_entities: async (manager, args) => formatToolResponse(await manager.createEntities(args.entities)),
|
|
16
|
+
delete_entities: async (manager, args) => {
|
|
17
|
+
await manager.deleteEntities(args.entityNames);
|
|
18
|
+
return formatTextResponse(`Deleted ${args.entityNames.length} entities`);
|
|
19
|
+
},
|
|
20
|
+
read_graph: async (manager) => formatToolResponse(await manager.readGraph()),
|
|
21
|
+
open_nodes: async (manager, args) => formatToolResponse(await manager.openNodes(args.names)),
|
|
22
|
+
// ==================== RELATION HANDLERS ====================
|
|
23
|
+
create_relations: async (manager, args) => formatToolResponse(await manager.createRelations(args.relations)),
|
|
24
|
+
delete_relations: async (manager, args) => {
|
|
25
|
+
await manager.deleteRelations(args.relations);
|
|
26
|
+
return formatTextResponse(`Deleted ${args.relations.length} relations`);
|
|
27
|
+
},
|
|
28
|
+
// ==================== OBSERVATION HANDLERS ====================
|
|
29
|
+
add_observations: async (manager, args) => formatToolResponse(await manager.addObservations(args.observations)),
|
|
30
|
+
delete_observations: async (manager, args) => {
|
|
31
|
+
await manager.deleteObservations(args.deletions);
|
|
32
|
+
return formatTextResponse('Observations deleted successfully');
|
|
33
|
+
},
|
|
34
|
+
// ==================== SEARCH HANDLERS ====================
|
|
35
|
+
search_nodes: async (manager, args) => formatToolResponse(await manager.searchNodes(args.query, args.tags, args.minImportance, args.maxImportance)),
|
|
36
|
+
search_by_date_range: async (manager, args) => formatToolResponse(await manager.searchByDateRange(args.startDate, args.endDate, args.entityType, args.tags)),
|
|
37
|
+
search_nodes_ranked: async (manager, args) => formatToolResponse(await manager.searchNodesRanked(args.query, args.tags, args.minImportance, args.maxImportance, args.limit)),
|
|
38
|
+
boolean_search: async (manager, args) => formatToolResponse(await manager.booleanSearch(args.query, args.tags, args.minImportance, args.maxImportance)),
|
|
39
|
+
fuzzy_search: async (manager, args) => formatToolResponse(await manager.fuzzySearch(args.query, args.threshold, args.tags, args.minImportance, args.maxImportance)),
|
|
40
|
+
get_search_suggestions: async (manager, args) => formatToolResponse(await manager.getSearchSuggestions(args.query, args.maxSuggestions)),
|
|
41
|
+
// ==================== SAVED SEARCH HANDLERS ====================
|
|
42
|
+
save_search: async (manager, args) => formatToolResponse(await manager.saveSearch(args)),
|
|
43
|
+
execute_saved_search: async (manager, args) => formatToolResponse(await manager.executeSavedSearch(args.name)),
|
|
44
|
+
list_saved_searches: async (manager) => formatToolResponse(await manager.listSavedSearches()),
|
|
45
|
+
delete_saved_search: async (manager, args) => {
|
|
46
|
+
const deleted = await manager.deleteSavedSearch(args.name);
|
|
47
|
+
return formatTextResponse(deleted
|
|
48
|
+
? `Saved search "${args.name}" deleted successfully`
|
|
49
|
+
: `Saved search "${args.name}" not found`);
|
|
50
|
+
},
|
|
51
|
+
update_saved_search: async (manager, args) => formatToolResponse(await manager.updateSavedSearch(args.name, args.updates)),
|
|
52
|
+
// ==================== TAG HANDLERS ====================
|
|
53
|
+
add_tags: async (manager, args) => formatToolResponse(await manager.addTags(args.entityName, args.tags)),
|
|
54
|
+
remove_tags: async (manager, args) => formatToolResponse(await manager.removeTags(args.entityName, args.tags)),
|
|
55
|
+
set_importance: async (manager, args) => formatToolResponse(await manager.setImportance(args.entityName, args.importance)),
|
|
56
|
+
add_tags_to_multiple_entities: async (manager, args) => formatToolResponse(await manager.addTagsToMultipleEntities(args.entityNames, args.tags)),
|
|
57
|
+
replace_tag: async (manager, args) => formatToolResponse(await manager.replaceTag(args.oldTag, args.newTag)),
|
|
58
|
+
merge_tags: async (manager, args) => formatToolResponse(await manager.mergeTags(args.tag1, args.tag2, args.targetTag)),
|
|
59
|
+
// ==================== TAG ALIAS HANDLERS ====================
|
|
60
|
+
add_tag_alias: async (manager, args) => formatToolResponse(await manager.addTagAlias(args.alias, args.canonical, args.description)),
|
|
61
|
+
list_tag_aliases: async (manager) => formatToolResponse(await manager.listTagAliases()),
|
|
62
|
+
remove_tag_alias: async (manager, args) => {
|
|
63
|
+
const removed = await manager.removeTagAlias(args.alias);
|
|
64
|
+
return formatTextResponse(removed
|
|
65
|
+
? `Tag alias "${args.alias}" removed successfully`
|
|
66
|
+
: `Tag alias "${args.alias}" not found`);
|
|
67
|
+
},
|
|
68
|
+
get_aliases_for_tag: async (manager, args) => formatToolResponse(await manager.getAliasesForTag(args.canonicalTag)),
|
|
69
|
+
resolve_tag: async (manager, args) => formatToolResponse({
|
|
70
|
+
tag: args.tag,
|
|
71
|
+
resolved: await manager.resolveTag(args.tag),
|
|
72
|
+
}),
|
|
73
|
+
// ==================== HIERARCHY HANDLERS ====================
|
|
74
|
+
set_entity_parent: async (manager, args) => formatToolResponse(await manager.setEntityParent(args.entityName, args.parentName)),
|
|
75
|
+
get_children: async (manager, args) => formatToolResponse(await manager.getChildren(args.entityName)),
|
|
76
|
+
get_parent: async (manager, args) => formatToolResponse(await manager.getParent(args.entityName)),
|
|
77
|
+
get_ancestors: async (manager, args) => formatToolResponse(await manager.getAncestors(args.entityName)),
|
|
78
|
+
get_descendants: async (manager, args) => formatToolResponse(await manager.getDescendants(args.entityName)),
|
|
79
|
+
get_subtree: async (manager, args) => formatToolResponse(await manager.getSubtree(args.entityName)),
|
|
80
|
+
get_root_entities: async (manager) => formatToolResponse(await manager.getRootEntities()),
|
|
81
|
+
get_entity_depth: async (manager, args) => formatToolResponse({
|
|
82
|
+
entityName: args.entityName,
|
|
83
|
+
depth: await manager.getEntityDepth(args.entityName),
|
|
84
|
+
}),
|
|
85
|
+
move_entity: async (manager, args) => formatToolResponse(await manager.moveEntity(args.entityName, args.newParentName)),
|
|
86
|
+
// ==================== ANALYTICS HANDLERS ====================
|
|
87
|
+
get_graph_stats: async (manager) => formatToolResponse(await manager.getGraphStats()),
|
|
88
|
+
validate_graph: async (manager) => formatToolResponse(await manager.validateGraph()),
|
|
89
|
+
// ==================== COMPRESSION HANDLERS ====================
|
|
90
|
+
find_duplicates: async (manager, args) => formatToolResponse(await manager.findDuplicates(args.threshold)),
|
|
91
|
+
merge_entities: async (manager, args) => formatToolResponse(await manager.mergeEntities(args.entityNames, args.targetName)),
|
|
92
|
+
compress_graph: async (manager, args) => formatToolResponse(await manager.compressGraph(args.threshold, args.dryRun)),
|
|
93
|
+
archive_entities: async (manager, args) => formatToolResponse(await manager.archiveEntities({
|
|
94
|
+
olderThan: args.olderThan,
|
|
95
|
+
importanceLessThan: args.importanceLessThan,
|
|
96
|
+
tags: args.tags,
|
|
97
|
+
}, args.dryRun)),
|
|
98
|
+
// ==================== IMPORT/EXPORT HANDLERS ====================
|
|
99
|
+
import_graph: async (manager, args) => formatToolResponse(await manager.importGraph(args.format, args.data, args.mergeStrategy, args.dryRun)),
|
|
100
|
+
export_graph: async (manager, args) => formatRawResponse(await manager.exportGraph(args.format, args.filter)),
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Handle a tool call by dispatching to the appropriate handler.
|
|
104
|
+
*
|
|
105
|
+
* @param name - Tool name to call
|
|
106
|
+
* @param args - Tool arguments
|
|
107
|
+
* @param manager - Knowledge graph manager instance
|
|
108
|
+
* @returns Tool response
|
|
109
|
+
* @throws Error if tool name is unknown
|
|
110
|
+
*/
|
|
111
|
+
export async function handleToolCall(name, args, manager) {
|
|
112
|
+
const handler = toolHandlers[name];
|
|
113
|
+
if (!handler) {
|
|
114
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
115
|
+
}
|
|
116
|
+
return handler(manager, args);
|
|
117
|
+
}
|
package/dist/utils/constants.js
CHANGED
|
@@ -59,6 +59,7 @@ export const LOG_PREFIXES = {
|
|
|
59
59
|
* Similarity scoring weights for duplicate detection.
|
|
60
60
|
* These weights determine the relative importance of each factor
|
|
61
61
|
* when calculating entity similarity for duplicate detection.
|
|
62
|
+
* Total weights must sum to 1.0 (100%).
|
|
62
63
|
*/
|
|
63
64
|
export const SIMILARITY_WEIGHTS = {
|
|
64
65
|
/** Name similarity weight (40%) - Uses Levenshtein distance */
|
|
@@ -66,9 +67,9 @@ export const SIMILARITY_WEIGHTS = {
|
|
|
66
67
|
/** Entity type match weight (20%) - Exact match required */
|
|
67
68
|
TYPE: 0.2,
|
|
68
69
|
/** Observation overlap weight (30%) - Uses Jaccard similarity */
|
|
69
|
-
|
|
70
|
+
OBSERVATIONS: 0.3,
|
|
70
71
|
/** Tag overlap weight (10%) - Uses Jaccard similarity */
|
|
71
|
-
|
|
72
|
+
TAGS: 0.1,
|
|
72
73
|
};
|
|
73
74
|
/**
|
|
74
75
|
* Default threshold for duplicate detection (80% similarity required).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Lookup and Utility Functions
|
|
3
|
+
*
|
|
4
|
+
* Centralizes entity lookup patterns to eliminate redundant code across managers.
|
|
5
|
+
* Provides type-safe entity finding with optional error throwing.
|
|
6
|
+
*/
|
|
7
|
+
import { EntityNotFoundError } from './errors.js';
|
|
8
|
+
export function findEntityByName(graph, name, throwIfNotFound = true) {
|
|
9
|
+
const entity = graph.entities.find(e => e.name === name);
|
|
10
|
+
if (!entity && throwIfNotFound) {
|
|
11
|
+
throw new EntityNotFoundError(name);
|
|
12
|
+
}
|
|
13
|
+
return entity ?? null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Finds multiple entities by name.
|
|
17
|
+
*
|
|
18
|
+
* @param graph - The knowledge graph to search
|
|
19
|
+
* @param names - Array of entity names to find
|
|
20
|
+
* @param throwIfAnyNotFound - Whether to throw if any entity doesn't exist (default: true)
|
|
21
|
+
* @returns Array of found entities (may be shorter than names if throwIfAnyNotFound is false)
|
|
22
|
+
* @throws EntityNotFoundError if any entity not found and throwIfAnyNotFound is true
|
|
23
|
+
*/
|
|
24
|
+
export function findEntitiesByNames(graph, names, throwIfAnyNotFound = true) {
|
|
25
|
+
const entities = [];
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
const entity = findEntityByName(graph, name, false);
|
|
28
|
+
if (entity) {
|
|
29
|
+
entities.push(entity);
|
|
30
|
+
}
|
|
31
|
+
else if (throwIfAnyNotFound) {
|
|
32
|
+
throw new EntityNotFoundError(name);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return entities;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Checks if an entity exists in the graph.
|
|
39
|
+
*
|
|
40
|
+
* @param graph - The knowledge graph to search
|
|
41
|
+
* @param name - The entity name to check
|
|
42
|
+
* @returns true if entity exists, false otherwise
|
|
43
|
+
*/
|
|
44
|
+
export function entityExists(graph, name) {
|
|
45
|
+
return graph.entities.some(e => e.name === name);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gets the index of an entity in the graph's entities array.
|
|
49
|
+
*
|
|
50
|
+
* @param graph - The knowledge graph to search
|
|
51
|
+
* @param name - The entity name to find
|
|
52
|
+
* @returns The index if found, -1 otherwise
|
|
53
|
+
*/
|
|
54
|
+
export function getEntityIndex(graph, name) {
|
|
55
|
+
return graph.entities.findIndex(e => e.name === name);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Removes an entity from the graph by name.
|
|
59
|
+
* Mutates the graph's entities array in place.
|
|
60
|
+
*
|
|
61
|
+
* @param graph - The knowledge graph to modify
|
|
62
|
+
* @param name - The entity name to remove
|
|
63
|
+
* @returns true if entity was removed, false if not found
|
|
64
|
+
*/
|
|
65
|
+
export function removeEntityByName(graph, name) {
|
|
66
|
+
const index = getEntityIndex(graph, name);
|
|
67
|
+
if (index === -1)
|
|
68
|
+
return false;
|
|
69
|
+
graph.entities.splice(index, 1);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Gets all entity names as a Set for fast lookup.
|
|
74
|
+
*
|
|
75
|
+
* @param graph - The knowledge graph
|
|
76
|
+
* @returns Set of all entity names
|
|
77
|
+
*/
|
|
78
|
+
export function getEntityNameSet(graph) {
|
|
79
|
+
return new Set(graph.entities.map(e => e.name));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Groups entities by their type.
|
|
83
|
+
*
|
|
84
|
+
* @param entities - Array of entities to group
|
|
85
|
+
* @returns Map of entity type to array of entities
|
|
86
|
+
*/
|
|
87
|
+
export function groupEntitiesByType(entities) {
|
|
88
|
+
const groups = new Map();
|
|
89
|
+
for (const entity of entities) {
|
|
90
|
+
const type = entity.entityType;
|
|
91
|
+
if (!groups.has(type)) {
|
|
92
|
+
groups.set(type, []);
|
|
93
|
+
}
|
|
94
|
+
groups.get(type).push(entity);
|
|
95
|
+
}
|
|
96
|
+
return groups;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Updates the lastModified timestamp on an entity.
|
|
100
|
+
* Mutates the entity in place.
|
|
101
|
+
*
|
|
102
|
+
* @param entity - The entity to update
|
|
103
|
+
* @returns The updated entity (same reference)
|
|
104
|
+
*/
|
|
105
|
+
export function touchEntity(entity) {
|
|
106
|
+
entity.lastModified = new Date().toISOString();
|
|
107
|
+
return entity;
|
|
108
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Filtering Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralizes common filtering logic for importance, date ranges, and other
|
|
5
|
+
* entity properties to eliminate duplicate patterns across search implementations.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Checks if an entity's importance is within the specified range.
|
|
9
|
+
* Entities without importance are treated as not matching if any filter is set.
|
|
10
|
+
*
|
|
11
|
+
* @param importance - The entity's importance value (may be undefined)
|
|
12
|
+
* @param minImportance - Minimum importance filter (inclusive)
|
|
13
|
+
* @param maxImportance - Maximum importance filter (inclusive)
|
|
14
|
+
* @returns true if importance is within range or no filters are set
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Check if entity passes importance filter
|
|
19
|
+
* if (isWithinImportanceRange(entity.importance, 5, 10)) {
|
|
20
|
+
* // Entity has importance between 5 and 10
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function isWithinImportanceRange(importance, minImportance, maxImportance) {
|
|
25
|
+
// If no filters set, always pass
|
|
26
|
+
if (minImportance === undefined && maxImportance === undefined) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
// Check minimum importance
|
|
30
|
+
if (minImportance !== undefined) {
|
|
31
|
+
if (importance === undefined || importance < minImportance) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Check maximum importance
|
|
36
|
+
if (maxImportance !== undefined) {
|
|
37
|
+
if (importance === undefined || importance > maxImportance) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Filters entities by importance range.
|
|
45
|
+
* Returns all entities if no importance filters are specified.
|
|
46
|
+
*
|
|
47
|
+
* @param entities - Array of entities to filter
|
|
48
|
+
* @param minImportance - Minimum importance filter (inclusive)
|
|
49
|
+
* @param maxImportance - Maximum importance filter (inclusive)
|
|
50
|
+
* @returns Filtered entities within the importance range
|
|
51
|
+
*/
|
|
52
|
+
export function filterByImportance(entities, minImportance, maxImportance) {
|
|
53
|
+
if (minImportance === undefined && maxImportance === undefined) {
|
|
54
|
+
return entities;
|
|
55
|
+
}
|
|
56
|
+
return entities.filter(e => isWithinImportanceRange(e.importance, minImportance, maxImportance));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Checks if a date value is within the specified range.
|
|
60
|
+
* Handles undefined values appropriately.
|
|
61
|
+
*
|
|
62
|
+
* @param dateValue - ISO 8601 date string to check (may be undefined)
|
|
63
|
+
* @param startDate - Start of date range (inclusive)
|
|
64
|
+
* @param endDate - End of date range (inclusive)
|
|
65
|
+
* @returns true if date is within range or no filters are set
|
|
66
|
+
*/
|
|
67
|
+
export function isWithinDateRange(dateValue, startDate, endDate) {
|
|
68
|
+
// If no filters set, always pass
|
|
69
|
+
if (!startDate && !endDate) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
// If date value is undefined but we have filters, fail
|
|
73
|
+
if (!dateValue) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const date = new Date(dateValue);
|
|
77
|
+
if (startDate && date < new Date(startDate)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (endDate && date > new Date(endDate)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Filters entities by creation date range.
|
|
87
|
+
*
|
|
88
|
+
* @param entities - Array of entities to filter
|
|
89
|
+
* @param startDate - Start of date range (inclusive)
|
|
90
|
+
* @param endDate - End of date range (inclusive)
|
|
91
|
+
* @returns Filtered entities created within the date range
|
|
92
|
+
*/
|
|
93
|
+
export function filterByCreatedDate(entities, startDate, endDate) {
|
|
94
|
+
if (!startDate && !endDate) {
|
|
95
|
+
return entities;
|
|
96
|
+
}
|
|
97
|
+
return entities.filter(e => isWithinDateRange(e.createdAt, startDate, endDate));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Filters entities by last modified date range.
|
|
101
|
+
*
|
|
102
|
+
* @param entities - Array of entities to filter
|
|
103
|
+
* @param startDate - Start of date range (inclusive)
|
|
104
|
+
* @param endDate - End of date range (inclusive)
|
|
105
|
+
* @returns Filtered entities modified within the date range
|
|
106
|
+
*/
|
|
107
|
+
export function filterByModifiedDate(entities, startDate, endDate) {
|
|
108
|
+
if (!startDate && !endDate) {
|
|
109
|
+
return entities;
|
|
110
|
+
}
|
|
111
|
+
return entities.filter(e => isWithinDateRange(e.lastModified, startDate, endDate));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Filters entities by entity type.
|
|
115
|
+
*
|
|
116
|
+
* @param entities - Array of entities to filter
|
|
117
|
+
* @param entityType - Entity type to filter by (case-sensitive)
|
|
118
|
+
* @returns Filtered entities of the specified type
|
|
119
|
+
*/
|
|
120
|
+
export function filterByEntityType(entities, entityType) {
|
|
121
|
+
if (!entityType) {
|
|
122
|
+
return entities;
|
|
123
|
+
}
|
|
124
|
+
return entities.filter(e => e.entityType === entityType);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Checks if an entity passes all the specified filters.
|
|
128
|
+
* Short-circuits on first failing filter for performance.
|
|
129
|
+
*
|
|
130
|
+
* Note: Tag filtering should be handled separately using tagUtils.hasMatchingTag
|
|
131
|
+
* as it requires special normalization logic.
|
|
132
|
+
*
|
|
133
|
+
* @param entity - Entity to check
|
|
134
|
+
* @param filters - Filters to apply
|
|
135
|
+
* @returns true if entity passes all filters
|
|
136
|
+
*/
|
|
137
|
+
export function entityPassesFilters(entity, filters) {
|
|
138
|
+
// Importance filter
|
|
139
|
+
if (!isWithinImportanceRange(entity.importance, filters.minImportance, filters.maxImportance)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
// Entity type filter
|
|
143
|
+
if (filters.entityType && entity.entityType !== filters.entityType) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
// Created date filter
|
|
147
|
+
if (!isWithinDateRange(entity.createdAt, filters.createdAfter, filters.createdBefore)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
// Modified date filter
|
|
151
|
+
if (!isWithinDateRange(entity.lastModified, filters.modifiedAfter, filters.modifiedBefore)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
package/dist/utils/index.js
CHANGED
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utilities Module Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* Centralizes all utility exports for convenient importing.
|
|
5
|
+
* Sprint 1 additions: responseFormatter, tagUtils, entityUtils,
|
|
6
|
+
* validationHelper, paginationUtils, filterUtils
|
|
3
7
|
*/
|
|
8
|
+
// Error types
|
|
4
9
|
export { KnowledgeGraphError, EntityNotFoundError, RelationNotFoundError, DuplicateEntityError, ValidationError, CycleDetectedError, InvalidImportanceError, FileOperationError, ImportError, ExportError, InsufficientEntitiesError, } from './errors.js';
|
|
10
|
+
// String utilities
|
|
5
11
|
export { levenshteinDistance } from './levenshtein.js';
|
|
6
12
|
export { calculateTF, calculateIDF, calculateTFIDF, tokenize } from './tfidf.js';
|
|
13
|
+
// Logging
|
|
7
14
|
export { logger } from './logger.js';
|
|
15
|
+
// Date utilities
|
|
8
16
|
export { isWithinDateRange, parseDateRange, isValidISODate, getCurrentTimestamp } from './dateUtils.js';
|
|
17
|
+
// Validation utilities
|
|
9
18
|
export { validateEntity, validateRelation, validateImportance, validateTags } from './validationUtils.js';
|
|
19
|
+
// Path utilities
|
|
10
20
|
export { defaultMemoryPath, ensureMemoryFilePath, validateFilePath } from './pathUtils.js';
|
|
21
|
+
// Constants
|
|
11
22
|
export { FILE_EXTENSIONS, FILE_SUFFIXES, DEFAULT_FILE_NAMES, ENV_VARS, DEFAULT_BASE_DIR, LOG_PREFIXES, SIMILARITY_WEIGHTS, DEFAULT_DUPLICATE_THRESHOLD, SEARCH_LIMITS, IMPORTANCE_RANGE, } from './constants.js';
|
|
23
|
+
// Zod schemas
|
|
12
24
|
export { EntitySchema, CreateEntitySchema, UpdateEntitySchema, RelationSchema, CreateRelationSchema, SearchQuerySchema, DateRangeSchema, TagAliasSchema, ExportFormatSchema, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, } from './schemas.js';
|
|
25
|
+
// Search cache
|
|
13
26
|
export { SearchCache, searchCaches, clearAllSearchCaches, getAllCacheStats, cleanupAllCaches, } from './searchCache.js';
|
|
27
|
+
// === Sprint 1: New Utility Exports ===
|
|
28
|
+
// MCP Response formatting (Task 1.1)
|
|
29
|
+
export { formatToolResponse, formatTextResponse, formatRawResponse, formatErrorResponse, } from './responseFormatter.js';
|
|
30
|
+
// Tag utilities (Task 1.2)
|
|
31
|
+
export { normalizeTag, normalizeTags, hasMatchingTag, hasAllTags, filterByTags, addUniqueTags, removeTags, } from './tagUtils.js';
|
|
32
|
+
// Entity utilities (Task 1.3)
|
|
33
|
+
export { findEntityByName, findEntitiesByNames, entityExists, getEntityIndex, removeEntityByName, getEntityNameSet, groupEntitiesByType, touchEntity, } from './entityUtils.js';
|
|
34
|
+
// Zod validation helpers (Task 1.4)
|
|
35
|
+
export { formatZodErrors, validateWithSchema, validateSafe, validateArrayWithSchema, } from './validationHelper.js';
|
|
36
|
+
// Pagination utilities (Task 1.5)
|
|
37
|
+
export { validatePagination, applyPagination, paginateArray, getPaginationMeta, } from './paginationUtils.js';
|
|
38
|
+
// Filter utilities (Task 1.6)
|
|
39
|
+
export { isWithinImportanceRange, filterByImportance, filterByCreatedDate, filterByModifiedDate, filterByEntityType, entityPassesFilters, } from './filterUtils.js';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralizes pagination validation and application logic
|
|
5
|
+
* to eliminate duplicate patterns across search implementations.
|
|
6
|
+
*/
|
|
7
|
+
import { SEARCH_LIMITS } from './constants.js';
|
|
8
|
+
/**
|
|
9
|
+
* Validates and normalizes pagination parameters.
|
|
10
|
+
* Ensures offset is non-negative and limit is within configured bounds.
|
|
11
|
+
*
|
|
12
|
+
* @param offset - Starting position (default: 0)
|
|
13
|
+
* @param limit - Maximum results to return (default: SEARCH_LIMITS.DEFAULT)
|
|
14
|
+
* @returns Validated pagination parameters with helper methods
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const pagination = validatePagination(10, 50);
|
|
19
|
+
* const results = items.slice(pagination.offset, pagination.offset + pagination.limit);
|
|
20
|
+
* if (pagination.hasMore(items.length)) {
|
|
21
|
+
* console.log('More results available');
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function validatePagination(offset = 0, limit = SEARCH_LIMITS.DEFAULT) {
|
|
26
|
+
const validatedOffset = Math.max(0, offset);
|
|
27
|
+
const validatedLimit = Math.min(Math.max(SEARCH_LIMITS.MIN, limit), SEARCH_LIMITS.MAX);
|
|
28
|
+
return {
|
|
29
|
+
offset: validatedOffset,
|
|
30
|
+
limit: validatedLimit,
|
|
31
|
+
hasMore: (totalCount) => validatedOffset + validatedLimit < totalCount,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Applies pagination to an array of items.
|
|
36
|
+
*
|
|
37
|
+
* @param items - Array to paginate
|
|
38
|
+
* @param pagination - Validated pagination parameters
|
|
39
|
+
* @returns Paginated slice of the array
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const pagination = validatePagination(offset, limit);
|
|
44
|
+
* const pageResults = applyPagination(allResults, pagination);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function applyPagination(items, pagination) {
|
|
48
|
+
return items.slice(pagination.offset, pagination.offset + pagination.limit);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Applies pagination using raw offset and limit values.
|
|
52
|
+
* Combines validation and application in one call.
|
|
53
|
+
*
|
|
54
|
+
* @param items - Array to paginate
|
|
55
|
+
* @param offset - Starting position
|
|
56
|
+
* @param limit - Maximum results
|
|
57
|
+
* @returns Paginated slice of the array
|
|
58
|
+
*/
|
|
59
|
+
export function paginateArray(items, offset = 0, limit = SEARCH_LIMITS.DEFAULT) {
|
|
60
|
+
const pagination = validatePagination(offset, limit);
|
|
61
|
+
return applyPagination(items, pagination);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Calculates pagination metadata for a result set.
|
|
65
|
+
*
|
|
66
|
+
* @param totalCount - Total number of items
|
|
67
|
+
* @param offset - Current offset
|
|
68
|
+
* @param limit - Current limit
|
|
69
|
+
* @returns Pagination metadata
|
|
70
|
+
*/
|
|
71
|
+
export function getPaginationMeta(totalCount, offset = 0, limit = SEARCH_LIMITS.DEFAULT) {
|
|
72
|
+
const pagination = validatePagination(offset, limit);
|
|
73
|
+
return {
|
|
74
|
+
totalCount,
|
|
75
|
+
offset: pagination.offset,
|
|
76
|
+
limit: pagination.limit,
|
|
77
|
+
hasMore: pagination.hasMore(totalCount),
|
|
78
|
+
pageNumber: Math.floor(pagination.offset / pagination.limit) + 1,
|
|
79
|
+
totalPages: Math.ceil(totalCount / pagination.limit),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Response Formatter Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralizes response formatting for MCP tool calls to eliminate
|
|
5
|
+
* redundant JSON.stringify patterns across the codebase.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Formats data as an MCP tool response with JSON content.
|
|
9
|
+
* Centralizes the response format to ensure consistency and reduce duplication.
|
|
10
|
+
*
|
|
11
|
+
* @param data - Any data to be JSON stringified
|
|
12
|
+
* @returns Formatted MCP tool response
|
|
13
|
+
*/
|
|
14
|
+
export function formatToolResponse(data) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Formats a simple text message as an MCP tool response.
|
|
21
|
+
* Use for success messages that don't need JSON formatting.
|
|
22
|
+
*
|
|
23
|
+
* @param message - Plain text message
|
|
24
|
+
* @returns Formatted MCP tool response
|
|
25
|
+
*/
|
|
26
|
+
export function formatTextResponse(message) {
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: 'text', text: message }],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Formats raw string content as an MCP tool response.
|
|
33
|
+
* Use for export formats that return pre-formatted strings (markdown, CSV, etc.)
|
|
34
|
+
*
|
|
35
|
+
* @param content - Raw string content
|
|
36
|
+
* @returns Formatted MCP tool response
|
|
37
|
+
*/
|
|
38
|
+
export function formatRawResponse(content) {
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: 'text', text: content }],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Formats an error as an MCP tool response with isError flag.
|
|
45
|
+
*
|
|
46
|
+
* @param error - Error object or message string
|
|
47
|
+
* @returns Formatted MCP tool error response
|
|
48
|
+
*/
|
|
49
|
+
export function formatErrorResponse(error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : error;
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: 'text', text: message }],
|
|
53
|
+
isError: true,
|
|
54
|
+
};
|
|
55
|
+
}
|