@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.
- package/CHANGELOG.md +542 -0
- package/LICENSE +21 -0
- package/README.md +1281 -0
- package/bin/mcp-memory-keeper +54 -0
- package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
- package/dist/__tests__/e2e/server-e2e.test.js +341 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +1054 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +411 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
- package/dist/__tests__/integration/git-integration.test.js +241 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
- package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
- package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
- package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
- package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +291 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +305 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
- package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/token-limits.test.js +225 -0
- package/dist/__tests__/utils/tool-profiles.test.js +374 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4425 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +2017 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +780 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/token-limits.js +350 -0
- package/dist/utils/tool-profiles.js +242 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/examples/config.json +31 -0
- package/examples/project-directory-setup.md +114 -0
- 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
|
+
}
|