@danielsimonjr/memory-mcp 0.7.2 → 0.47.1
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__/edge-cases/edge-cases.test.js +406 -0
- package/dist/__tests__/file-path.test.js +5 -5
- package/dist/__tests__/integration/workflows.test.js +449 -0
- package/dist/__tests__/knowledge-graph.test.js +8 -3
- package/dist/__tests__/performance/benchmarks.test.js +413 -0
- package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
- package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
- package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
- package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
- package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
- package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
- package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
- package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
- package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
- package/dist/core/EntityManager.js +554 -0
- package/dist/core/GraphStorage.js +172 -0
- package/dist/core/KnowledgeGraphManager.js +423 -0
- package/dist/core/ObservationManager.js +129 -0
- package/dist/core/RelationManager.js +186 -0
- package/dist/core/TransactionManager.js +389 -0
- package/dist/core/index.js +9 -0
- package/dist/features/AnalyticsManager.js +222 -0
- package/dist/features/ArchiveManager.js +74 -0
- package/dist/features/BackupManager.js +311 -0
- package/dist/features/CompressionManager.js +291 -0
- package/dist/features/ExportManager.js +305 -0
- package/dist/features/HierarchyManager.js +219 -0
- package/dist/features/ImportExportManager.js +50 -0
- package/dist/features/ImportManager.js +328 -0
- package/dist/features/TagManager.js +210 -0
- package/dist/features/index.js +12 -0
- package/dist/index.js +13 -996
- package/dist/memory.jsonl +18 -0
- package/dist/search/BasicSearch.js +131 -0
- package/dist/search/BooleanSearch.js +283 -0
- package/dist/search/FuzzySearch.js +96 -0
- package/dist/search/RankedSearch.js +190 -0
- package/dist/search/SavedSearchManager.js +145 -0
- package/dist/search/SearchFilterChain.js +187 -0
- package/dist/search/SearchManager.js +305 -0
- package/dist/search/SearchSuggestions.js +57 -0
- package/dist/search/TFIDFIndexManager.js +217 -0
- package/dist/search/index.js +14 -0
- package/dist/server/MCPServer.js +52 -0
- package/dist/server/toolDefinitions.js +732 -0
- package/dist/server/toolHandlers.js +117 -0
- package/dist/types/analytics.types.js +6 -0
- package/dist/types/entity.types.js +7 -0
- package/dist/types/import-export.types.js +7 -0
- package/dist/types/index.js +12 -0
- package/dist/types/search.types.js +7 -0
- package/dist/types/tag.types.js +6 -0
- package/dist/utils/constants.js +128 -0
- package/dist/utils/dateUtils.js +89 -0
- package/dist/utils/entityUtils.js +108 -0
- package/dist/utils/errors.js +121 -0
- package/dist/utils/filterUtils.js +155 -0
- package/dist/utils/index.js +39 -0
- package/dist/utils/levenshtein.js +62 -0
- package/dist/utils/logger.js +33 -0
- package/dist/utils/paginationUtils.js +81 -0
- package/dist/utils/pathUtils.js +115 -0
- package/dist/utils/responseFormatter.js +55 -0
- package/dist/utils/schemas.js +184 -0
- package/dist/utils/searchCache.js +209 -0
- package/dist/utils/tagUtils.js +107 -0
- package/dist/utils/tfidf.js +90 -0
- package/dist/utils/validationHelper.js +99 -0
- package/dist/utils/validationUtils.js +109 -0
- package/package.json +82 -48
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
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
|
|
7
|
+
*/
|
|
8
|
+
// Error types
|
|
9
|
+
export { KnowledgeGraphError, EntityNotFoundError, RelationNotFoundError, DuplicateEntityError, ValidationError, CycleDetectedError, InvalidImportanceError, FileOperationError, ImportError, ExportError, InsufficientEntitiesError, } from './errors.js';
|
|
10
|
+
// String utilities
|
|
11
|
+
export { levenshteinDistance } from './levenshtein.js';
|
|
12
|
+
export { calculateTF, calculateIDF, calculateTFIDF, tokenize } from './tfidf.js';
|
|
13
|
+
// Logging
|
|
14
|
+
export { logger } from './logger.js';
|
|
15
|
+
// Date utilities
|
|
16
|
+
export { isWithinDateRange, parseDateRange, isValidISODate, getCurrentTimestamp } from './dateUtils.js';
|
|
17
|
+
// Validation utilities
|
|
18
|
+
export { validateEntity, validateRelation, validateImportance, validateTags } from './validationUtils.js';
|
|
19
|
+
// Path utilities
|
|
20
|
+
export { defaultMemoryPath, ensureMemoryFilePath, validateFilePath } from './pathUtils.js';
|
|
21
|
+
// Constants
|
|
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
|
|
24
|
+
export { EntitySchema, CreateEntitySchema, UpdateEntitySchema, RelationSchema, CreateRelationSchema, SearchQuerySchema, DateRangeSchema, TagAliasSchema, ExportFormatSchema, BatchCreateEntitiesSchema, BatchCreateRelationsSchema, EntityNamesSchema, DeleteRelationsSchema, } from './schemas.js';
|
|
25
|
+
// Search cache
|
|
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,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levenshtein Distance Algorithm
|
|
3
|
+
*
|
|
4
|
+
* Calculates the minimum number of single-character edits (insertions,
|
|
5
|
+
* deletions, or substitutions) needed to change one string into another.
|
|
6
|
+
*
|
|
7
|
+
* Used for fuzzy string matching to find similar entities despite typos.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/levenshtein
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Calculate Levenshtein distance between two strings.
|
|
13
|
+
*
|
|
14
|
+
* Returns the minimum number of single-character edits needed to change
|
|
15
|
+
* one word into another.
|
|
16
|
+
*
|
|
17
|
+
* **Algorithm**: Dynamic programming with O(m*n) time and space complexity,
|
|
18
|
+
* where m and n are the lengths of the two strings.
|
|
19
|
+
*
|
|
20
|
+
* @param str1 - First string to compare
|
|
21
|
+
* @param str2 - Second string to compare
|
|
22
|
+
* @returns Minimum number of edits required (0 = identical strings)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* levenshteinDistance("kitten", "sitting"); // Returns 3
|
|
27
|
+
* levenshteinDistance("hello", "hello"); // Returns 0
|
|
28
|
+
* levenshteinDistance("abc", ""); // Returns 3
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function levenshteinDistance(str1, str2) {
|
|
32
|
+
const m = str1.length;
|
|
33
|
+
const n = str2.length;
|
|
34
|
+
// Create 2D array for dynamic programming
|
|
35
|
+
const dp = Array(m + 1)
|
|
36
|
+
.fill(null)
|
|
37
|
+
.map(() => Array(n + 1).fill(0));
|
|
38
|
+
// Initialize base cases: distance from empty string
|
|
39
|
+
for (let i = 0; i <= m; i++) {
|
|
40
|
+
dp[i][0] = i;
|
|
41
|
+
}
|
|
42
|
+
for (let j = 0; j <= n; j++) {
|
|
43
|
+
dp[0][j] = j;
|
|
44
|
+
}
|
|
45
|
+
// Fill dp table
|
|
46
|
+
for (let i = 1; i <= m; i++) {
|
|
47
|
+
for (let j = 1; j <= n; j++) {
|
|
48
|
+
if (str1[i - 1] === str2[j - 1]) {
|
|
49
|
+
// Characters match, no edit needed
|
|
50
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Take minimum of three operations
|
|
54
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, // deletion
|
|
55
|
+
dp[i][j - 1] + 1, // insertion
|
|
56
|
+
dp[i - 1][j - 1] + 1 // substitution
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return dp[m][n];
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple logging utility for the Memory MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent log formatting with levels: debug, info, warn, error
|
|
5
|
+
*/
|
|
6
|
+
export const logger = {
|
|
7
|
+
/**
|
|
8
|
+
* Debug level logging (verbose, for development)
|
|
9
|
+
*/
|
|
10
|
+
debug: (msg, ...args) => {
|
|
11
|
+
if (process.env.LOG_LEVEL === 'debug') {
|
|
12
|
+
console.debug(`[DEBUG] ${msg}`, ...args);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
/**
|
|
16
|
+
* Info level logging (general informational messages)
|
|
17
|
+
*/
|
|
18
|
+
info: (msg, ...args) => {
|
|
19
|
+
console.log(`[INFO] ${msg}`, ...args);
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Warning level logging (warnings that don't prevent operation)
|
|
23
|
+
*/
|
|
24
|
+
warn: (msg, ...args) => {
|
|
25
|
+
console.warn(`[WARN] ${msg}`, ...args);
|
|
26
|
+
},
|
|
27
|
+
/**
|
|
28
|
+
* Error level logging (errors that affect functionality)
|
|
29
|
+
*/
|
|
30
|
+
error: (msg, ...args) => {
|
|
31
|
+
console.error(`[ERROR] ${msg}`, ...args);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -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,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for file path management and backward compatibility.
|
|
5
|
+
* Handles memory file path resolution with environment variable support.
|
|
6
|
+
*
|
|
7
|
+
* @module utils/pathUtils
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { FileOperationError } from './errors.js';
|
|
13
|
+
/**
|
|
14
|
+
* Validate and normalize a file path to prevent path traversal attacks.
|
|
15
|
+
*
|
|
16
|
+
* This function:
|
|
17
|
+
* - Normalizes the path to canonical form
|
|
18
|
+
* - Converts relative paths to absolute paths
|
|
19
|
+
* - Detects and prevents path traversal attempts (..)
|
|
20
|
+
*
|
|
21
|
+
* @param filePath - The file path to validate
|
|
22
|
+
* @param baseDir - Optional base directory for relative paths (defaults to process.cwd())
|
|
23
|
+
* @returns Validated absolute file path
|
|
24
|
+
* @throws {FileOperationError} If path traversal is detected or path is invalid
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* // Valid paths
|
|
29
|
+
* validateFilePath('/var/data/memory.jsonl'); // Returns absolute path
|
|
30
|
+
* validateFilePath('data/memory.jsonl'); // Returns absolute path from cwd
|
|
31
|
+
*
|
|
32
|
+
* // Invalid paths (throws FileOperationError)
|
|
33
|
+
* validateFilePath('../../../etc/passwd'); // Path traversal detected
|
|
34
|
+
* validateFilePath('/var/data/../../../etc/passwd'); // Path traversal detected
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function validateFilePath(filePath, baseDir = process.cwd()) {
|
|
38
|
+
// Normalize path to remove redundant separators and resolve . and ..
|
|
39
|
+
const normalized = path.normalize(filePath);
|
|
40
|
+
// Convert to absolute path
|
|
41
|
+
const absolute = path.isAbsolute(normalized)
|
|
42
|
+
? normalized
|
|
43
|
+
: path.join(baseDir, normalized);
|
|
44
|
+
// After normalization, check if path still contains .. which would indicate
|
|
45
|
+
// traversal beyond the base directory
|
|
46
|
+
const finalNormalized = path.normalize(absolute);
|
|
47
|
+
// Split path into segments and check for suspicious patterns
|
|
48
|
+
const segments = finalNormalized.split(path.sep);
|
|
49
|
+
if (segments.includes('..')) {
|
|
50
|
+
throw new FileOperationError(`Path traversal detected in file path: ${filePath}`, filePath);
|
|
51
|
+
}
|
|
52
|
+
return finalNormalized;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Default memory file path (in same directory as compiled code).
|
|
56
|
+
*/
|
|
57
|
+
export const defaultMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../memory.jsonl');
|
|
58
|
+
/**
|
|
59
|
+
* Ensure memory file path with backward compatibility migration.
|
|
60
|
+
*
|
|
61
|
+
* Handles:
|
|
62
|
+
* 1. Custom MEMORY_FILE_PATH environment variable (with path traversal protection)
|
|
63
|
+
* 2. Backward compatibility: migrates memory.json to memory.jsonl
|
|
64
|
+
* 3. Absolute vs relative path resolution
|
|
65
|
+
*
|
|
66
|
+
* @returns Resolved and validated memory file path
|
|
67
|
+
* @throws {FileOperationError} If path traversal is detected in MEMORY_FILE_PATH
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* // Use environment variable
|
|
72
|
+
* process.env.MEMORY_FILE_PATH = '/data/memory.jsonl';
|
|
73
|
+
* const path = await ensureMemoryFilePath(); // '/data/memory.jsonl'
|
|
74
|
+
*
|
|
75
|
+
* // Use default path
|
|
76
|
+
* delete process.env.MEMORY_FILE_PATH;
|
|
77
|
+
* const path = await ensureMemoryFilePath(); // './memory.jsonl'
|
|
78
|
+
*
|
|
79
|
+
* // Invalid path (throws error)
|
|
80
|
+
* process.env.MEMORY_FILE_PATH = '../../../etc/passwd';
|
|
81
|
+
* await ensureMemoryFilePath(); // Throws FileOperationError
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export async function ensureMemoryFilePath() {
|
|
85
|
+
if (process.env.MEMORY_FILE_PATH) {
|
|
86
|
+
// Custom path provided, validate and resolve to absolute
|
|
87
|
+
const baseDir = path.dirname(fileURLToPath(import.meta.url)) + '/../';
|
|
88
|
+
const validatedPath = validateFilePath(process.env.MEMORY_FILE_PATH, baseDir);
|
|
89
|
+
return validatedPath;
|
|
90
|
+
}
|
|
91
|
+
// No custom path set, check for backward compatibility migration
|
|
92
|
+
const oldMemoryPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../memory.json');
|
|
93
|
+
const newMemoryPath = defaultMemoryPath;
|
|
94
|
+
try {
|
|
95
|
+
// Check if old file exists
|
|
96
|
+
await fs.access(oldMemoryPath);
|
|
97
|
+
try {
|
|
98
|
+
// Check if new file exists
|
|
99
|
+
await fs.access(newMemoryPath);
|
|
100
|
+
// Both files exist, use new one (no migration needed)
|
|
101
|
+
return newMemoryPath;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Old file exists, new file doesn't - migrate
|
|
105
|
+
console.log('[INFO] Found legacy memory.json file, migrating to memory.jsonl for JSONL format compatibility');
|
|
106
|
+
await fs.rename(oldMemoryPath, newMemoryPath);
|
|
107
|
+
console.log('[INFO] Successfully migrated memory.json to memory.jsonl');
|
|
108
|
+
return newMemoryPath;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Old file doesn't exist, use new path
|
|
113
|
+
return newMemoryPath;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -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
|
+
}
|