@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.
Files changed (70) hide show
  1. package/dist/__tests__/edge-cases/edge-cases.test.js +406 -0
  2. package/dist/__tests__/file-path.test.js +5 -5
  3. package/dist/__tests__/integration/workflows.test.js +449 -0
  4. package/dist/__tests__/knowledge-graph.test.js +8 -3
  5. package/dist/__tests__/performance/benchmarks.test.js +413 -0
  6. package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
  7. package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
  8. package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
  9. package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
  10. package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
  11. package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
  12. package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
  13. package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
  14. package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
  15. package/dist/core/EntityManager.js +554 -0
  16. package/dist/core/GraphStorage.js +172 -0
  17. package/dist/core/KnowledgeGraphManager.js +423 -0
  18. package/dist/core/ObservationManager.js +129 -0
  19. package/dist/core/RelationManager.js +186 -0
  20. package/dist/core/TransactionManager.js +389 -0
  21. package/dist/core/index.js +9 -0
  22. package/dist/features/AnalyticsManager.js +222 -0
  23. package/dist/features/ArchiveManager.js +74 -0
  24. package/dist/features/BackupManager.js +311 -0
  25. package/dist/features/CompressionManager.js +291 -0
  26. package/dist/features/ExportManager.js +305 -0
  27. package/dist/features/HierarchyManager.js +219 -0
  28. package/dist/features/ImportExportManager.js +50 -0
  29. package/dist/features/ImportManager.js +328 -0
  30. package/dist/features/TagManager.js +210 -0
  31. package/dist/features/index.js +12 -0
  32. package/dist/index.js +13 -996
  33. package/dist/memory.jsonl +18 -0
  34. package/dist/search/BasicSearch.js +131 -0
  35. package/dist/search/BooleanSearch.js +283 -0
  36. package/dist/search/FuzzySearch.js +96 -0
  37. package/dist/search/RankedSearch.js +190 -0
  38. package/dist/search/SavedSearchManager.js +145 -0
  39. package/dist/search/SearchFilterChain.js +187 -0
  40. package/dist/search/SearchManager.js +305 -0
  41. package/dist/search/SearchSuggestions.js +57 -0
  42. package/dist/search/TFIDFIndexManager.js +217 -0
  43. package/dist/search/index.js +14 -0
  44. package/dist/server/MCPServer.js +52 -0
  45. package/dist/server/toolDefinitions.js +732 -0
  46. package/dist/server/toolHandlers.js +117 -0
  47. package/dist/types/analytics.types.js +6 -0
  48. package/dist/types/entity.types.js +7 -0
  49. package/dist/types/import-export.types.js +7 -0
  50. package/dist/types/index.js +12 -0
  51. package/dist/types/search.types.js +7 -0
  52. package/dist/types/tag.types.js +6 -0
  53. package/dist/utils/constants.js +128 -0
  54. package/dist/utils/dateUtils.js +89 -0
  55. package/dist/utils/entityUtils.js +108 -0
  56. package/dist/utils/errors.js +121 -0
  57. package/dist/utils/filterUtils.js +155 -0
  58. package/dist/utils/index.js +39 -0
  59. package/dist/utils/levenshtein.js +62 -0
  60. package/dist/utils/logger.js +33 -0
  61. package/dist/utils/paginationUtils.js +81 -0
  62. package/dist/utils/pathUtils.js +115 -0
  63. package/dist/utils/responseFormatter.js +55 -0
  64. package/dist/utils/schemas.js +184 -0
  65. package/dist/utils/searchCache.js +209 -0
  66. package/dist/utils/tagUtils.js +107 -0
  67. package/dist/utils/tfidf.js +90 -0
  68. package/dist/utils/validationHelper.js +99 -0
  69. package/dist/utils/validationUtils.js +109 -0
  70. 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
+ }