@smallironman/mcp-memory-keeper 0.12.2-fork1

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 (110) hide show
  1. package/CHANGELOG.md +542 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1281 -0
  4. package/bin/mcp-memory-keeper +54 -0
  5. package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
  6. package/dist/__tests__/e2e/server-e2e.test.js +341 -0
  7. package/dist/__tests__/helpers/database-test-helper.js +160 -0
  8. package/dist/__tests__/helpers/test-server.js +92 -0
  9. package/dist/__tests__/integration/advanced-features.test.js +614 -0
  10. package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
  11. package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
  12. package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
  13. package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
  14. package/dist/__tests__/integration/channels.test.js +376 -0
  15. package/dist/__tests__/integration/checkpoint.test.js +251 -0
  16. package/dist/__tests__/integration/concurrent-access.test.js +190 -0
  17. package/dist/__tests__/integration/context-operations.test.js +243 -0
  18. package/dist/__tests__/integration/contextDiff.test.js +852 -0
  19. package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
  20. package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
  21. package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
  22. package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
  23. package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
  24. package/dist/__tests__/integration/contextSearch.test.js +1054 -0
  25. package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
  26. package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
  27. package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
  28. package/dist/__tests__/integration/database-initialization.test.js +134 -0
  29. package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
  30. package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
  31. package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
  32. package/dist/__tests__/integration/error-cases.test.js +411 -0
  33. package/dist/__tests__/integration/export-import.test.js +367 -0
  34. package/dist/__tests__/integration/feature-flags.test.js +542 -0
  35. package/dist/__tests__/integration/file-operations.test.js +264 -0
  36. package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
  37. package/dist/__tests__/integration/git-integration.test.js +241 -0
  38. package/dist/__tests__/integration/index-tools.test.js +496 -0
  39. package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
  40. package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
  41. package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
  42. package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
  43. package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
  44. package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
  45. package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
  46. package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
  47. package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
  48. package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
  49. package/dist/__tests__/integration/migrations.test.js +528 -0
  50. package/dist/__tests__/integration/multi-agent.test.js +546 -0
  51. package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
  52. package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
  53. package/dist/__tests__/integration/project-directory.test.js +291 -0
  54. package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
  55. package/dist/__tests__/integration/retention.test.js +513 -0
  56. package/dist/__tests__/integration/search.test.js +333 -0
  57. package/dist/__tests__/integration/semantic-search.test.js +266 -0
  58. package/dist/__tests__/integration/server-initialization.test.js +305 -0
  59. package/dist/__tests__/integration/session-management.test.js +219 -0
  60. package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
  61. package/dist/__tests__/integration/smart-compaction.test.js +230 -0
  62. package/dist/__tests__/integration/summarization.test.js +308 -0
  63. package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
  64. package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
  65. package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
  66. package/dist/__tests__/security/input-validation.test.js +115 -0
  67. package/dist/__tests__/utils/agents.test.js +473 -0
  68. package/dist/__tests__/utils/database.test.js +177 -0
  69. package/dist/__tests__/utils/git.test.js +122 -0
  70. package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
  71. package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
  72. package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
  73. package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
  74. package/dist/__tests__/utils/token-limits.test.js +225 -0
  75. package/dist/__tests__/utils/tool-profiles.test.js +374 -0
  76. package/dist/__tests__/utils/validation.test.js +200 -0
  77. package/dist/__tests__/utils/vector-store.test.js +231 -0
  78. package/dist/handlers/contextWatchHandlers.js +206 -0
  79. package/dist/index.js +4425 -0
  80. package/dist/migrations/003_add_channels.js +174 -0
  81. package/dist/migrations/004_add_context_watch.js +151 -0
  82. package/dist/migrations/005_add_context_watch.js +98 -0
  83. package/dist/migrations/simplify-sharing.js +117 -0
  84. package/dist/repositories/BaseRepository.js +30 -0
  85. package/dist/repositories/CheckpointRepository.js +140 -0
  86. package/dist/repositories/ContextRepository.js +2017 -0
  87. package/dist/repositories/FileRepository.js +104 -0
  88. package/dist/repositories/RepositoryManager.js +62 -0
  89. package/dist/repositories/SessionRepository.js +66 -0
  90. package/dist/repositories/WatcherRepository.js +252 -0
  91. package/dist/repositories/index.js +15 -0
  92. package/dist/test-helpers/database-helper.js +128 -0
  93. package/dist/types/entities.js +3 -0
  94. package/dist/utils/agents.js +791 -0
  95. package/dist/utils/channels.js +150 -0
  96. package/dist/utils/database.js +780 -0
  97. package/dist/utils/feature-flags.js +476 -0
  98. package/dist/utils/git.js +145 -0
  99. package/dist/utils/knowledge-graph.js +264 -0
  100. package/dist/utils/migrationHealthCheck.js +373 -0
  101. package/dist/utils/migrations.js +452 -0
  102. package/dist/utils/retention.js +460 -0
  103. package/dist/utils/timestamps.js +112 -0
  104. package/dist/utils/token-limits.js +350 -0
  105. package/dist/utils/tool-profiles.js +242 -0
  106. package/dist/utils/validation.js +296 -0
  107. package/dist/utils/vector-store.js +247 -0
  108. package/examples/config.json +31 -0
  109. package/examples/project-directory-setup.md +114 -0
  110. package/package.json +85 -0
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ /**
3
+ * Token limit management utilities for MCP Memory Keeper
4
+ *
5
+ * Provides dynamic calculation of safe limits based on actual content
6
+ * instead of relying on hardcoded values scattered throughout the codebase.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.DEFAULT_TOKEN_CONFIG = exports.TOKEN_WARNING_THRESHOLD = void 0;
10
+ exports.estimateTokens = estimateTokens;
11
+ exports.calculateSize = calculateSize;
12
+ exports.estimateResponseOverhead = estimateResponseOverhead;
13
+ exports.calculateSafeItemLimit = calculateSafeItemLimit;
14
+ exports.calculateDynamicDefaultLimit = calculateDynamicDefaultLimit;
15
+ exports.checkTokenLimit = checkTokenLimit;
16
+ exports.getTokenConfig = getTokenConfig;
17
+ // Validation bounds for environment variables
18
+ const VALIDATION_BOUNDS = {
19
+ MAX_TOKENS: { MIN: 1000, MAX: 100000 },
20
+ SAFETY_BUFFER: { MIN: 0.1, MAX: 1.0 },
21
+ MIN_ITEMS: { MIN: 1, MAX: 100 },
22
+ MAX_ITEMS: { MIN: 10, MAX: 1000 },
23
+ CHARS_PER_TOKEN: { MIN: 2.5, MAX: 5.0 }, // Advanced: token estimation ratio
24
+ };
25
+ // SQL query limits
26
+ const QUERY_LIMITS = {
27
+ SAMPLE_SIZE: 10, // Items to sample for average size calculation
28
+ };
29
+ // JSON formatting
30
+ const JSON_INDENT_SPACES = 2;
31
+ // Warning threshold for token usage (70% of limit)
32
+ exports.TOKEN_WARNING_THRESHOLD = 0.7;
33
+ /** Default configuration values */
34
+ exports.DEFAULT_TOKEN_CONFIG = {
35
+ mcpMaxTokens: 25000,
36
+ safetyBuffer: 0.8, // Use only 80% of the limit for safety
37
+ minItems: 1,
38
+ maxItems: 100,
39
+ charsPerToken: 3.5, // Conservative estimate for JSON content
40
+ };
41
+ /**
42
+ * Estimates token count for a given text
43
+ * @param text The text to estimate tokens for
44
+ * @param charsPerToken Optional character-to-token ratio (default: 3.5)
45
+ * @returns Estimated number of tokens
46
+ *
47
+ * Token estimation is based on empirical analysis of OpenAI's tokenization:
48
+ * - OpenAI's guideline: ~4 characters per token for English text
49
+ * - Default uses 3.5 chars/token for more conservative estimation
50
+ * - This accounts for JSON formatting, special characters, and metadata overhead
51
+ * - Conservative estimation prevents unexpected token limit errors
52
+ * - Can be configured via MCP_CHARS_PER_TOKEN environment variable
53
+ */
54
+ function estimateTokens(text, charsPerToken = exports.DEFAULT_TOKEN_CONFIG.charsPerToken) {
55
+ return Math.ceil(text.length / charsPerToken);
56
+ }
57
+ /**
58
+ * Calculate the size of a value in bytes
59
+ */
60
+ function calculateSize(value) {
61
+ return Buffer.byteLength(value, 'utf8');
62
+ }
63
+ /**
64
+ * Estimates the overhead of the response structure
65
+ * @param itemCount Number of items in the response
66
+ * @param includeMetadata Whether metadata fields are included
67
+ * @param config Optional token configuration
68
+ * @returns Estimated tokens for the response wrapper
69
+ */
70
+ function estimateResponseOverhead(itemCount, includeMetadata = false, config = exports.DEFAULT_TOKEN_CONFIG) {
71
+ // Base pagination structure
72
+ const basePagination = {
73
+ total: 0,
74
+ returned: 0,
75
+ offset: 0,
76
+ hasMore: false,
77
+ nextOffset: null,
78
+ totalCount: 0,
79
+ page: 1,
80
+ pageSize: 0,
81
+ totalPages: 1,
82
+ hasNextPage: false,
83
+ hasPreviousPage: false,
84
+ previousOffset: null,
85
+ totalSize: 0,
86
+ averageSize: 0,
87
+ defaultsApplied: { limit: true, sort: true },
88
+ truncated: false,
89
+ truncatedCount: 0,
90
+ };
91
+ // Extended fields when metadata is included
92
+ if (includeMetadata) {
93
+ // Additional overhead for metadata fields:
94
+ // - Timestamps (created_at, updated_at): ~40 chars each
95
+ // - Size field: ~10 chars
96
+ // - Warning messages: up to 200 chars
97
+ // - JSON formatting: ~100 chars
98
+ // Total additional overhead: ~200 tokens
99
+ const METADATA_OVERHEAD_TOKENS = 200;
100
+ return (estimateTokens(JSON.stringify(basePagination), config.charsPerToken) +
101
+ METADATA_OVERHEAD_TOKENS);
102
+ }
103
+ return estimateTokens(JSON.stringify(basePagination), config.charsPerToken);
104
+ }
105
+ /**
106
+ * Calculate the safe number of items that can be returned
107
+ * @param items Sample items to calculate with
108
+ * @param includeMetadata Whether metadata will be included
109
+ * @param config Token limit configuration
110
+ * @returns Safe number of items to return
111
+ */
112
+ function calculateSafeItemLimit(items, includeMetadata = false, config = exports.DEFAULT_TOKEN_CONFIG) {
113
+ if (items.length === 0)
114
+ return 0;
115
+ // Calculate safe token limit (with buffer)
116
+ const safeTokenLimit = Math.floor(config.mcpMaxTokens * config.safetyBuffer);
117
+ // Calculate overhead
118
+ const responseOverhead = estimateResponseOverhead(items.length, includeMetadata, config);
119
+ // Calculate average item size from a sample
120
+ // Sample size of 10 items provides good statistical representation
121
+ // while keeping calculation overhead minimal
122
+ const SAMPLE_SIZE = 10;
123
+ const sampleSize = Math.min(SAMPLE_SIZE, items.length);
124
+ const sampleItems = items.slice(0, sampleSize);
125
+ // Helper to safely parse JSON metadata
126
+ const parseMetadata = (metadata) => {
127
+ if (!metadata)
128
+ return null;
129
+ if (typeof metadata === 'object')
130
+ return metadata;
131
+ try {
132
+ return JSON.parse(metadata);
133
+ }
134
+ catch (error) {
135
+ console.warn('Invalid JSON in metadata, using null:', error);
136
+ return null;
137
+ }
138
+ };
139
+ // Transform items if metadata is included
140
+ const itemsForCalculation = includeMetadata
141
+ ? sampleItems.map(item => ({
142
+ key: item.key,
143
+ value: item.value,
144
+ category: item.category,
145
+ priority: item.priority,
146
+ channel: item.channel,
147
+ metadata: parseMetadata(item.metadata),
148
+ size: item.size || calculateSize(item.value || ''),
149
+ created_at: item.created_at,
150
+ updated_at: item.updated_at,
151
+ }))
152
+ : sampleItems;
153
+ // Calculate average tokens per item
154
+ const totalSampleTokens = itemsForCalculation.reduce((sum, item) => {
155
+ return (sum + estimateTokens(JSON.stringify(item, null, JSON_INDENT_SPACES), config.charsPerToken));
156
+ }, 0);
157
+ const avgTokensPerItem = Math.ceil(totalSampleTokens / sampleSize);
158
+ // Calculate how many items can fit
159
+ const availableTokens = safeTokenLimit - responseOverhead;
160
+ const safeItemCount = Math.floor(availableTokens / avgTokensPerItem);
161
+ // Apply min/max constraints
162
+ const finalCount = Math.max(config.minItems, Math.min(safeItemCount, config.maxItems, items.length));
163
+ // Log calculation details for debugging
164
+ if (process.env.MCP_DEBUG_LOGGING) {
165
+ console.log('[Token Calculation]', {
166
+ safeTokenLimit,
167
+ responseOverhead,
168
+ avgTokensPerItem,
169
+ availableTokens,
170
+ calculatedCount: safeItemCount,
171
+ finalCount,
172
+ includeMetadata,
173
+ });
174
+ }
175
+ return finalCount;
176
+ }
177
+ /**
178
+ * Dynamically calculate the default limit based on typical item sizes
179
+ * @param sessionId Session to analyze
180
+ * @param includeMetadata Whether metadata will be included
181
+ * @param db Database connection
182
+ * @returns Calculated safe default limit
183
+ */
184
+ function calculateDynamicDefaultLimit(sessionId, includeMetadata, db) {
185
+ try {
186
+ // Get a sample of recent items to calculate average size
187
+ const sampleQuery = `
188
+ SELECT * FROM context_items
189
+ WHERE session_id = ?
190
+ ORDER BY created_at DESC
191
+ LIMIT ${QUERY_LIMITS.SAMPLE_SIZE}
192
+ `;
193
+ const sampleItems = db.prepare(sampleQuery).all(sessionId);
194
+ if (sampleItems.length === 0) {
195
+ // No items yet, use conservative defaults
196
+ // With metadata: 30 items (typically ~800 chars each = ~6,800 tokens)
197
+ // Without metadata: 100 items (typically ~200 chars each = ~5,700 tokens)
198
+ // These defaults ensure first queries don't exceed limits
199
+ const DEFAULT_LIMIT_WITH_METADATA = 30;
200
+ const DEFAULT_LIMIT_WITHOUT_METADATA = 100;
201
+ return includeMetadata ? DEFAULT_LIMIT_WITH_METADATA : DEFAULT_LIMIT_WITHOUT_METADATA;
202
+ }
203
+ // Calculate safe limit based on actual data
204
+ const safeLimit = calculateSafeItemLimit(sampleItems, includeMetadata);
205
+ // Round to nearest 10 for cleaner limits and better user experience
206
+ // Minimum of 10 items to ensure useful results even with large items
207
+ const ROUNDING_FACTOR = 10;
208
+ const MIN_DYNAMIC_LIMIT = 10;
209
+ return Math.max(MIN_DYNAMIC_LIMIT, Math.floor(safeLimit / ROUNDING_FACTOR) * ROUNDING_FACTOR);
210
+ }
211
+ catch (error) {
212
+ // Fallback to conservative defaults on error
213
+ console.error('Error calculating dynamic limit:', error);
214
+ const FALLBACK_LIMIT_WITH_METADATA = 30;
215
+ const FALLBACK_LIMIT_WITHOUT_METADATA = 100;
216
+ return includeMetadata ? FALLBACK_LIMIT_WITH_METADATA : FALLBACK_LIMIT_WITHOUT_METADATA;
217
+ }
218
+ }
219
+ /**
220
+ * Check if a response would exceed token limits
221
+ * @param items Items to be returned
222
+ * @param includeMetadata Whether metadata is included
223
+ * @param config Token limit configuration
224
+ * @returns Object with exceedsLimit flag and estimated tokens
225
+ */
226
+ function checkTokenLimit(items, includeMetadata = false, config = exports.DEFAULT_TOKEN_CONFIG) {
227
+ // Helper to safely parse JSON metadata (reused from calculateSafeItemLimit)
228
+ const parseMetadata = (metadata) => {
229
+ if (!metadata)
230
+ return null;
231
+ if (typeof metadata === 'object')
232
+ return metadata;
233
+ try {
234
+ return JSON.parse(metadata);
235
+ }
236
+ catch (error) {
237
+ console.warn('Invalid JSON in metadata, using null:', error);
238
+ return null;
239
+ }
240
+ };
241
+ // Transform items if needed
242
+ const itemsForCalculation = includeMetadata
243
+ ? items.map(item => ({
244
+ key: item.key,
245
+ value: item.value,
246
+ category: item.category,
247
+ priority: item.priority,
248
+ channel: item.channel,
249
+ metadata: parseMetadata(item.metadata),
250
+ size: item.size || calculateSize(item.value || ''),
251
+ created_at: item.created_at,
252
+ updated_at: item.updated_at,
253
+ }))
254
+ : items;
255
+ // Build full response structure matching actual handler response
256
+ const response = {
257
+ items: itemsForCalculation,
258
+ pagination: {
259
+ total: items.length,
260
+ returned: items.length,
261
+ offset: 0,
262
+ hasMore: false,
263
+ nextOffset: null,
264
+ truncated: false,
265
+ truncatedCount: 0,
266
+ warning: undefined,
267
+ },
268
+ };
269
+ const responseJson = JSON.stringify(response, null, JSON_INDENT_SPACES);
270
+ const estimatedTokens = estimateTokens(responseJson, config.charsPerToken);
271
+ const safeLimit = Math.floor(config.mcpMaxTokens * config.safetyBuffer);
272
+ const exceedsLimit = estimatedTokens > safeLimit;
273
+ const safeItemCount = exceedsLimit
274
+ ? calculateSafeItemLimit(items, includeMetadata, config)
275
+ : items.length;
276
+ return { exceedsLimit, estimatedTokens, safeItemCount };
277
+ }
278
+ /**
279
+ * Get configuration from environment or use defaults
280
+ * Validates all environment variables to ensure they're within reasonable bounds
281
+ */
282
+ function getTokenConfig() {
283
+ const config = { ...exports.DEFAULT_TOKEN_CONFIG };
284
+ // Validate and apply MCP_MAX_TOKENS
285
+ if (process.env.MCP_MAX_TOKENS) {
286
+ const maxTokens = parseInt(process.env.MCP_MAX_TOKENS, 10);
287
+ if (!isNaN(maxTokens) &&
288
+ maxTokens >= VALIDATION_BOUNDS.MAX_TOKENS.MIN &&
289
+ maxTokens <= VALIDATION_BOUNDS.MAX_TOKENS.MAX) {
290
+ config.mcpMaxTokens = maxTokens;
291
+ }
292
+ else {
293
+ console.warn(`Invalid MCP_MAX_TOKENS (${process.env.MCP_MAX_TOKENS}), using default ${exports.DEFAULT_TOKEN_CONFIG.mcpMaxTokens}`);
294
+ }
295
+ }
296
+ // Validate and apply MCP_TOKEN_SAFETY_BUFFER
297
+ if (process.env.MCP_TOKEN_SAFETY_BUFFER) {
298
+ const buffer = parseFloat(process.env.MCP_TOKEN_SAFETY_BUFFER);
299
+ if (!isNaN(buffer) &&
300
+ buffer >= VALIDATION_BOUNDS.SAFETY_BUFFER.MIN &&
301
+ buffer <= VALIDATION_BOUNDS.SAFETY_BUFFER.MAX) {
302
+ config.safetyBuffer = buffer;
303
+ }
304
+ else {
305
+ console.warn(`Invalid MCP_TOKEN_SAFETY_BUFFER (${process.env.MCP_TOKEN_SAFETY_BUFFER}), using default ${exports.DEFAULT_TOKEN_CONFIG.safetyBuffer}`);
306
+ }
307
+ }
308
+ // Validate and apply MCP_MIN_ITEMS
309
+ if (process.env.MCP_MIN_ITEMS) {
310
+ const minItems = parseInt(process.env.MCP_MIN_ITEMS, 10);
311
+ if (!isNaN(minItems) &&
312
+ minItems >= VALIDATION_BOUNDS.MIN_ITEMS.MIN &&
313
+ minItems <= VALIDATION_BOUNDS.MIN_ITEMS.MAX) {
314
+ config.minItems = minItems;
315
+ }
316
+ else {
317
+ console.warn(`Invalid MCP_MIN_ITEMS (${process.env.MCP_MIN_ITEMS}), using default ${exports.DEFAULT_TOKEN_CONFIG.minItems}`);
318
+ }
319
+ }
320
+ // Validate and apply MCP_MAX_ITEMS
321
+ if (process.env.MCP_MAX_ITEMS) {
322
+ const maxItems = parseInt(process.env.MCP_MAX_ITEMS, 10);
323
+ if (!isNaN(maxItems) &&
324
+ maxItems >= VALIDATION_BOUNDS.MAX_ITEMS.MIN &&
325
+ maxItems <= VALIDATION_BOUNDS.MAX_ITEMS.MAX) {
326
+ config.maxItems = maxItems;
327
+ }
328
+ else {
329
+ console.warn(`Invalid MCP_MAX_ITEMS (${process.env.MCP_MAX_ITEMS}), using default ${exports.DEFAULT_TOKEN_CONFIG.maxItems}`);
330
+ }
331
+ }
332
+ // Validate and apply MCP_CHARS_PER_TOKEN (advanced setting)
333
+ if (process.env.MCP_CHARS_PER_TOKEN) {
334
+ const charsPerToken = parseFloat(process.env.MCP_CHARS_PER_TOKEN);
335
+ if (!isNaN(charsPerToken) &&
336
+ charsPerToken >= VALIDATION_BOUNDS.CHARS_PER_TOKEN.MIN &&
337
+ charsPerToken <= VALIDATION_BOUNDS.CHARS_PER_TOKEN.MAX) {
338
+ config.charsPerToken = charsPerToken;
339
+ }
340
+ else {
341
+ console.warn(`Invalid MCP_CHARS_PER_TOKEN (${process.env.MCP_CHARS_PER_TOKEN}), using default ${exports.DEFAULT_TOKEN_CONFIG.charsPerToken}. Valid range: ${VALIDATION_BOUNDS.CHARS_PER_TOKEN.MIN}-${VALIDATION_BOUNDS.CHARS_PER_TOKEN.MAX}`);
342
+ }
343
+ }
344
+ // Ensure min <= max
345
+ if (config.minItems > config.maxItems) {
346
+ console.warn(`MCP_MIN_ITEMS (${config.minItems}) > MCP_MAX_ITEMS (${config.maxItems}), swapping values`);
347
+ [config.minItems, config.maxItems] = [config.maxItems, config.minItems];
348
+ }
349
+ return config;
350
+ }
@@ -0,0 +1,242 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DEFAULT_PROFILES = exports.ALL_TOOL_NAMES_SET = exports.ALL_TOOL_NAMES = void 0;
37
+ exports.loadConfigFile = loadConfigFile;
38
+ exports.validateToolNames = validateToolNames;
39
+ exports.resolveActiveProfile = resolveActiveProfile;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
43
+ /**
44
+ * All known tool names - source of truth for validation.
45
+ * Update this list whenever a tool is added or removed from the
46
+ * ListToolsRequestSchema handler in src/index.ts.
47
+ */
48
+ exports.ALL_TOOL_NAMES = [
49
+ // Session Management
50
+ 'context_session_start',
51
+ 'context_session_list',
52
+ 'context_set_project_dir',
53
+ // Core Context
54
+ 'context_save',
55
+ 'context_get',
56
+ 'context_status',
57
+ // File Caching
58
+ 'context_cache_file',
59
+ 'context_file_changed',
60
+ // Checkpoints
61
+ 'context_checkpoint',
62
+ 'context_restore_checkpoint',
63
+ // Summarization & Compaction
64
+ 'context_summarize',
65
+ 'context_prepare_compaction',
66
+ // Git Integration
67
+ 'context_git_commit',
68
+ // Search
69
+ 'context_search',
70
+ 'context_search_all',
71
+ 'context_semantic_search',
72
+ // Export/Import
73
+ 'context_export',
74
+ 'context_import',
75
+ // Knowledge Graph
76
+ 'context_analyze',
77
+ 'context_find_related',
78
+ 'context_visualize',
79
+ // Multi-Agent
80
+ 'context_delegate',
81
+ // Session Branching/Merging
82
+ 'context_branch_session',
83
+ 'context_merge_sessions',
84
+ // Journal & Timeline
85
+ 'context_journal_entry',
86
+ 'context_timeline',
87
+ // Advanced Features
88
+ 'context_compress',
89
+ 'context_integrate_tool',
90
+ 'context_diff',
91
+ // Channel Management
92
+ 'context_list_channels',
93
+ 'context_channel_stats',
94
+ 'context_reassign_channel',
95
+ // Watch
96
+ 'context_watch',
97
+ // Batch Operations
98
+ 'context_batch_save',
99
+ 'context_batch_delete',
100
+ 'context_batch_update',
101
+ // Relationships
102
+ 'context_link',
103
+ 'context_get_related',
104
+ ];
105
+ /** Pre-computed Set for O(1) lookups — used internally and exported for index.ts */
106
+ exports.ALL_TOOL_NAMES_SET = new Set(exports.ALL_TOOL_NAMES);
107
+ /** Built-in default profiles */
108
+ exports.DEFAULT_PROFILES = {
109
+ minimal: [
110
+ 'context_session_start',
111
+ 'context_session_list',
112
+ 'context_save',
113
+ 'context_get',
114
+ 'context_search',
115
+ 'context_status',
116
+ 'context_checkpoint',
117
+ 'context_restore_checkpoint',
118
+ ],
119
+ standard: [
120
+ 'context_session_start',
121
+ 'context_session_list',
122
+ 'context_set_project_dir',
123
+ 'context_save',
124
+ 'context_get',
125
+ 'context_status',
126
+ 'context_checkpoint',
127
+ 'context_restore_checkpoint',
128
+ 'context_search',
129
+ 'context_search_all',
130
+ 'context_summarize',
131
+ 'context_prepare_compaction',
132
+ 'context_git_commit',
133
+ 'context_export',
134
+ 'context_import',
135
+ 'context_journal_entry',
136
+ 'context_timeline',
137
+ 'context_list_channels',
138
+ 'context_channel_stats',
139
+ 'context_batch_save',
140
+ 'context_batch_delete',
141
+ 'context_batch_update',
142
+ ],
143
+ full: [...exports.ALL_TOOL_NAMES],
144
+ };
145
+ const CONFIG_DIR = path.join(os.homedir(), '.mcp-memory-keeper');
146
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
147
+ /** Load config file, returning null if absent or invalid */
148
+ function loadConfigFile(configPath = CONFIG_FILE) {
149
+ try {
150
+ if (!fs.existsSync(configPath)) {
151
+ return null;
152
+ }
153
+ const raw = fs.readFileSync(configPath, 'utf-8');
154
+ const parsed = JSON.parse(raw);
155
+ if (!parsed ||
156
+ typeof parsed !== 'object' ||
157
+ Array.isArray(parsed) ||
158
+ !parsed.profiles ||
159
+ typeof parsed.profiles !== 'object' ||
160
+ Array.isArray(parsed.profiles)) {
161
+ console.warn(`[MCP-Memory-Keeper] Config file at ${configPath} is missing a valid "profiles" key. Ignoring file.`);
162
+ return null;
163
+ }
164
+ // Validated: parsed.profiles is a non-null, non-array object
165
+ return { profiles: parsed.profiles };
166
+ }
167
+ catch (error) {
168
+ const message = error instanceof Error ? error.message : String(error);
169
+ console.warn(`[MCP-Memory-Keeper] Failed to load config file at ${configPath}: ${message}. Ignoring file.`);
170
+ return null;
171
+ }
172
+ }
173
+ /** Validate tool names against ALL_TOOL_NAMES, returning unknown names */
174
+ function validateToolNames(tools) {
175
+ return tools.filter(name => !exports.ALL_TOOL_NAMES_SET.has(name));
176
+ }
177
+ /** Resolve the active profile based on env var and config file */
178
+ function resolveActiveProfile(configPath) {
179
+ const warnings = [];
180
+ let profileName = (process.env.TOOL_PROFILE || '').trim();
181
+ const hasEnvVar = profileName.length > 0;
182
+ if (!hasEnvVar) {
183
+ profileName = 'full';
184
+ }
185
+ const config = loadConfigFile(configPath);
186
+ let toolList;
187
+ let source = 'default';
188
+ // Resolution precedence: config file > built-in defaults
189
+ if (config && config.profiles[profileName] !== undefined) {
190
+ const candidate = config.profiles[profileName];
191
+ // Validate that the profile value is actually an array of strings
192
+ if (!Array.isArray(candidate) || !candidate.every(item => typeof item === 'string')) {
193
+ warnings.push(`Profile "${profileName}" in config file is not a valid array of strings. Falling back to built-in default.`);
194
+ }
195
+ else {
196
+ toolList = candidate;
197
+ source = hasEnvVar ? 'env+config' : 'config';
198
+ }
199
+ }
200
+ if (toolList === undefined && exports.DEFAULT_PROFILES[profileName] !== undefined) {
201
+ toolList = [...exports.DEFAULT_PROFILES[profileName]];
202
+ source = hasEnvVar ? 'env+builtin' : 'default';
203
+ }
204
+ if (toolList === undefined) {
205
+ // Profile not found anywhere
206
+ const allProfiles = {
207
+ ...exports.DEFAULT_PROFILES,
208
+ ...(config ? config.profiles : {}),
209
+ };
210
+ const profileList = Object.entries(allProfiles)
211
+ .map(([name, tools]) => `${name}(${Array.isArray(tools) ? tools.length : '?'})`)
212
+ .join(', ');
213
+ warnings.push(`Unknown TOOL_PROFILE "${profileName}". Available profiles: ${profileList}. Using "full".`);
214
+ profileName = 'full';
215
+ toolList = [...exports.DEFAULT_PROFILES.full];
216
+ source = 'default';
217
+ }
218
+ // Validate tool names
219
+ const unknownNames = validateToolNames(toolList);
220
+ if (unknownNames.length > 0) {
221
+ warnings.push(`Unknown tool names in profile "${profileName}": ${unknownNames.join(', ')}. These will be ignored.`);
222
+ }
223
+ // Filter to only valid tools
224
+ const validTools = toolList.filter(name => exports.ALL_TOOL_NAMES_SET.has(name));
225
+ // Guard against empty profile
226
+ if (validTools.length === 0) {
227
+ warnings.push(`Profile "${profileName}" has no valid tools after filtering. Using "full".`);
228
+ profileName = 'full';
229
+ return {
230
+ profileName,
231
+ tools: new Set(exports.ALL_TOOL_NAMES),
232
+ source: 'default',
233
+ warnings,
234
+ };
235
+ }
236
+ return {
237
+ profileName,
238
+ tools: new Set(validTools),
239
+ source,
240
+ warnings,
241
+ };
242
+ }