mcp-memory-keeper 0.10.1 → 0.10.2

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 CHANGED
@@ -7,7 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.10.1] - 2025-01-11
10
+ ## [0.10.2] - 2025-09-16
11
+
12
+ ### Fixed
13
+
14
+ - **Critical Token Limit Issue (#24)** - Fixed token overflow with includeMetadata
15
+ - Implemented dynamic token limit calculation based on actual content size
16
+ - Automatically adjusts item limits based on average item size in session
17
+ - More accurate token estimation (3.5 chars/token vs 4)
18
+ - Configurable via environment variables (MCP_MAX_TOKENS, MCP_TOKEN_SAFETY_BUFFER)
19
+ - Added tokenInfo to response metadata for transparency
20
+ - Resolves "MCP tool context_get response exceeds maximum allowed tokens" errors
21
+
22
+ ### Added
23
+
24
+ - **Token Limit Management Module** (`utils/token-limits.ts`)
25
+ - Dynamic calculation of safe item limits
26
+ - Response overhead estimation
27
+ - Configurable token limits via environment
28
+ - Better visibility into token usage
29
+ - Proper TypeScript interfaces for context items
30
+ - Environment variable validation with bounds checking
31
+ - Safe JSON parsing with error handling
32
+ - Well-documented constants replacing magic numbers
33
+
34
+ ## [0.10.1] - 2025-07-11
11
35
 
12
36
  ### Fixed
13
37
 
@@ -63,7 +87,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
63
87
  - Added recipes for common patterns in RECIPES.md
64
88
  - Added troubleshooting tips for new features
65
89
 
66
- ## [0.10.0] - 2025-01-26
90
+ ## [0.10.0] - 2025-06-26
67
91
 
68
92
  ### Added
69
93
 
@@ -102,7 +126,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
102
126
  - Enhanced validation for channel names
103
127
  - Backward compatible - existing items default to 'default' channel
104
128
 
105
- ## [0.9.0] - 2025-01-20
129
+ ## [0.9.0] - 2025-06-21
106
130
 
107
131
  ### Changed (BREAKING)
108
132
 
@@ -125,7 +149,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
125
149
  - `context_get_shared` tool (use `context_get` instead)
126
150
  - Complex sharing mechanism that was causing inconsistencies
127
151
 
128
- ## [0.8.4] - 2025-01-19
152
+ ## [0.8.4] - 2025-06-19
129
153
 
130
154
  ### Fixed
131
155
 
@@ -139,7 +163,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
139
163
  - Feature flags system (planned)
140
164
  - Database migration system (planned)
141
165
 
142
- ## [0.8.3] - 2025-01-19
166
+ ## [0.8.3] - 2025-06-19
143
167
 
144
168
  ### Added
145
169
 
@@ -160,7 +184,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
160
184
 
161
185
  - Automatic schema migration for existing databases to add the `working_directory` column
162
186
 
163
- ## [0.8.0] - 2024-01-18
187
+ ## [0.8.0] - 2025-06-18
164
188
 
165
189
  ### Added
166
190
 
@@ -200,7 +224,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
200
224
  - New tables: `journal_entries`, `compressed_context`, `tool_events`
201
225
  - All 255 tests passing
202
226
 
203
- ## [0.7.0] - 2024-01-18
227
+ ## [0.7.0] - 2025-06-18
204
228
 
205
229
  ### Added
206
230
 
@@ -224,7 +248,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
224
248
  - Added comprehensive test coverage (30 new tests)
225
249
  - All 236 tests passing
226
250
 
227
- ## [0.6.0] - 2024-01-17
251
+ ## [0.6.0] - 2025-06-17
228
252
 
229
253
  ### Added
230
254
 
@@ -247,7 +271,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
247
271
  - Comprehensive test coverage for semantic search
248
272
  - All 206 tests passing
249
273
 
250
- ## [0.5.0] - 2024-01-17
274
+ ## [0.5.0] - 2025-06-17
251
275
 
252
276
  ### Added
253
277
 
@@ -270,7 +294,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
270
294
  - Added `knowledge-graph.ts` utility module
271
295
  - Comprehensive test coverage for graph operations
272
296
 
273
- ## [0.4.2] - 2024-01-16
297
+ ## [0.4.2] - 2025-06-17
274
298
 
275
299
  ### Added
276
300
 
@@ -284,7 +308,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
284
308
  - Git integration error handling
285
309
  - Session list date filtering
286
310
 
287
- ## [0.4.1] - 2024-01-16
311
+ ## [0.4.1] - 2025-06-17
288
312
 
289
313
  ### Fixed
290
314
 
@@ -297,7 +321,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
297
321
  - Improved error messages for better debugging
298
322
  - Enhanced validation for file paths
299
323
 
300
- ## [0.4.0] - 2024-01-15
324
+ ## [0.4.0] - 2025-06-17
301
325
 
302
326
  ### Added
303
327
 
@@ -318,7 +342,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
318
342
  - Created `git.ts` utility module
319
343
  - 97% test coverage maintained
320
344
 
321
- ## [0.3.0] - 2024-01-14
345
+ ## [0.3.0] - 2025-06-17
322
346
 
323
347
  ### Added
324
348
 
@@ -347,7 +371,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
347
371
  - Implemented streaming for large exports
348
372
  - Transaction support for atomic operations
349
373
 
350
- ## [0.2.0] - 2024-01-13
374
+ ## [0.2.0] - 2025-06-17
351
375
 
352
376
  ### Added
353
377
 
@@ -377,7 +401,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
377
401
  - Memory leak in file cache operations
378
402
  - Session switching race condition
379
403
 
380
- ## [0.1.0] - 2024-01-12
404
+ ## [0.1.0] - 2025-06-17
381
405
 
382
406
  ### Added
383
407
 
@@ -405,12 +429,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
405
429
 
406
430
  ## Development Releases
407
431
 
408
- ### [0.1.0-beta.2] - 2024-01-11
432
+ ### [0.1.0-beta.2] - 2025-06-17
409
433
 
410
434
  - Fixed Windows path handling
411
435
  - Added Node.js 18+ compatibility
412
436
 
413
- ### [0.1.0-beta.1] - 2024-01-10
437
+ ### [0.1.0-beta.1] - 2025-06-17
414
438
 
415
439
  - Initial beta release
416
440
  - Basic functionality testing
package/README.md CHANGED
@@ -183,10 +183,36 @@ claude mcp add memory-keeper node /absolute/path/to/mcp-memory-keeper/dist/index
183
183
 
184
184
  ### Environment Variables
185
185
 
186
+ #### Storage and Installation
187
+
186
188
  - `DATA_DIR` - Directory for database storage (default: `~/mcp-data/memory-keeper/`)
187
189
  - `MEMORY_KEEPER_INSTALL_DIR` - Installation directory (default: `~/.local/mcp-servers/memory-keeper/`)
188
190
  - `MEMORY_KEEPER_AUTO_UPDATE` - Set to `1` to enable auto-updates
189
191
 
192
+ #### Token Limit Configuration
193
+
194
+ - `MCP_MAX_TOKENS` - Maximum tokens allowed in responses (default: `25000`, range: `1000-100000`)
195
+ - Adjust this if your MCP client has different limits
196
+ - `MCP_TOKEN_SAFETY_BUFFER` - Safety buffer percentage (default: `0.8`, range: `0.1-1.0`)
197
+ - Uses only this fraction of the max tokens to prevent overflows
198
+ - `MCP_MIN_ITEMS` - Minimum items to return even if exceeding limits (default: `1`, range: `1-100`)
199
+ - Ensures at least some results are returned
200
+ - `MCP_MAX_ITEMS` - Maximum items allowed per response (default: `100`, range: `10-1000`)
201
+ - Upper bound for result sets regardless of token limits
202
+ - `MCP_CHARS_PER_TOKEN` - Characters per token ratio (default: `3.5`, range: `2.5-5.0`) **[Advanced]**
203
+ - Adjusts token estimation accuracy for different content types
204
+ - Lower values = more conservative (safer but returns fewer items)
205
+ - Higher values = more aggressive (returns more items but risks overflow)
206
+
207
+ Example configuration for stricter token limits:
208
+
209
+ ```bash
210
+ export MCP_MAX_TOKENS=20000 # Lower max tokens
211
+ export MCP_TOKEN_SAFETY_BUFFER=0.7 # More conservative buffer
212
+ export MCP_MAX_ITEMS=50 # Fewer items per response
213
+ export MCP_CHARS_PER_TOKEN=3.0 # More conservative estimation (optional)
214
+ ```
215
+
190
216
  ### Claude Code (CLI)
191
217
 
192
218
  #### Configuration Scopes
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ // Final test to verify the fix for issue #24
3
+ // Simulates the exact scenario: context_get with sessionId: "current" and includeMetadata: true
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const database_1 = require("../../utils/database");
39
+ const RepositoryManager_1 = require("../../repositories/RepositoryManager");
40
+ const os = __importStar(require("os"));
41
+ const path = __importStar(require("path"));
42
+ const fs = __importStar(require("fs"));
43
+ // Mock functions from index.ts
44
+ function estimateTokens(text) {
45
+ return Math.ceil(text.length / 4);
46
+ }
47
+ function calculateSize(value) {
48
+ return Buffer.byteLength(value, 'utf8');
49
+ }
50
+ function validatePaginationParams(params) {
51
+ const errors = [];
52
+ let limit = 25; // default
53
+ let offset = 0; // default
54
+ // Validate limit
55
+ if (params.limit !== undefined && params.limit !== null) {
56
+ const rawLimit = params.limit;
57
+ if (!Number.isInteger(rawLimit) || rawLimit <= 0) {
58
+ errors.push(`Invalid limit: expected positive integer, got ${typeof rawLimit} '${rawLimit}'`);
59
+ }
60
+ else {
61
+ limit = Math.min(Math.max(1, rawLimit), 100); // clamp between 1-100
62
+ }
63
+ }
64
+ // Validate offset
65
+ if (params.offset !== undefined && params.offset !== null) {
66
+ const rawOffset = params.offset;
67
+ if (!Number.isInteger(rawOffset) || rawOffset < 0) {
68
+ errors.push(`Invalid offset: expected non-negative integer, got ${typeof rawOffset} '${rawOffset}'`);
69
+ }
70
+ else {
71
+ offset = rawOffset;
72
+ }
73
+ }
74
+ return { limit, offset, errors };
75
+ }
76
+ describe('Issue #24 - Final Fix Verification', () => {
77
+ let dbManager;
78
+ let repositories;
79
+ let tempDbPath;
80
+ let testSessionId;
81
+ beforeEach(() => {
82
+ tempDbPath = path.join(os.tmpdir(), `test-issue24-final-${Date.now()}.db`);
83
+ dbManager = new database_1.DatabaseManager({
84
+ filename: tempDbPath,
85
+ maxSize: 10 * 1024 * 1024,
86
+ walMode: true,
87
+ });
88
+ repositories = new RepositoryManager_1.RepositoryManager(dbManager);
89
+ // Create test session (simulating "current" session)
90
+ const session = repositories.sessions.create({
91
+ name: 'Current Session',
92
+ description: 'Simulating the current active session',
93
+ });
94
+ testSessionId = session.id;
95
+ // Create many realistic items that would cause overflow
96
+ const largeContent = `
97
+ This is a realistic context item saved during development.
98
+ It contains implementation details, code snippets, notes, and documentation.
99
+ The content is substantial to simulate real-world usage patterns.
100
+ Developers often save detailed context about complex features, debugging sessions,
101
+ architectural decisions, API documentation, and troubleshooting information.
102
+ `.trim();
103
+ for (let i = 0; i < 200; i++) {
104
+ repositories.contexts.save(testSessionId, {
105
+ key: `context_${String(i).padStart(3, '0')}`,
106
+ value: `${largeContent}\n\nSpecific item ${i} notes and details.`,
107
+ category: ['task', 'decision', 'progress', 'note'][i % 4],
108
+ priority: ['high', 'normal', 'low'][i % 3],
109
+ channel: `channel-${i % 10}`,
110
+ metadata: JSON.stringify({
111
+ index: i,
112
+ timestamp: new Date().toISOString(),
113
+ tags: ['dev', 'test', 'review'][i % 3],
114
+ }),
115
+ });
116
+ }
117
+ });
118
+ afterEach(() => {
119
+ dbManager.close();
120
+ try {
121
+ fs.unlinkSync(tempDbPath);
122
+ fs.unlinkSync(`${tempDbPath}-wal`);
123
+ fs.unlinkSync(`${tempDbPath}-shm`);
124
+ }
125
+ catch (_e) {
126
+ // Ignore
127
+ }
128
+ });
129
+ it('should handle context_get with includeMetadata: true without exceeding token limit', () => {
130
+ // Simulate the exact call from the issue:
131
+ // context_get(sessionId: "current", includeMetadata: true)
132
+ const args = {
133
+ sessionId: testSessionId, // Would be resolved from "current"
134
+ includeMetadata: true,
135
+ // No limit specified - should use new default of 30
136
+ };
137
+ // Simulate what the handler does
138
+ const includeMetadata = args.includeMetadata;
139
+ const rawLimit = undefined; // Not specified in the call
140
+ const rawOffset = undefined;
141
+ // Apply our fix: default limit is 30 when includeMetadata is true
142
+ const defaultLimit = includeMetadata ? 30 : 100;
143
+ const paginationValidation = validatePaginationParams({
144
+ limit: rawLimit !== undefined ? rawLimit : defaultLimit,
145
+ offset: rawOffset,
146
+ });
147
+ const { limit, offset } = paginationValidation;
148
+ console.log(`Using limit: ${limit} (includeMetadata: ${includeMetadata})`);
149
+ // Query with the calculated limit
150
+ const result = repositories.contexts.queryEnhanced({
151
+ sessionId: testSessionId,
152
+ includeMetadata: true,
153
+ limit: limit,
154
+ offset: offset,
155
+ });
156
+ console.log(`Retrieved ${result.items.length} of ${result.totalCount} items`);
157
+ // Transform items with metadata
158
+ const itemsWithMetadata = result.items.map(item => ({
159
+ key: item.key,
160
+ value: item.value,
161
+ category: item.category,
162
+ priority: item.priority,
163
+ channel: item.channel,
164
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
165
+ size: item.size || calculateSize(item.value),
166
+ created_at: item.created_at,
167
+ updated_at: item.updated_at,
168
+ }));
169
+ // Calculate metrics for token checking
170
+ const itemsForMetrics = itemsWithMetadata;
171
+ const jsonString = JSON.stringify(itemsForMetrics);
172
+ const estimatedTokens = estimateTokens(jsonString);
173
+ console.log(`Items token estimate: ${estimatedTokens}`);
174
+ // Build full response
175
+ const response = {
176
+ items: itemsWithMetadata,
177
+ pagination: {
178
+ total: result.totalCount,
179
+ returned: result.items.length,
180
+ offset: 0,
181
+ hasMore: result.totalCount > result.items.length,
182
+ nextOffset: result.items.length < result.totalCount ? result.items.length : null,
183
+ totalCount: result.totalCount,
184
+ page: 1,
185
+ pageSize: limit,
186
+ totalPages: Math.ceil(result.totalCount / limit),
187
+ hasNextPage: result.totalCount > result.items.length,
188
+ hasPreviousPage: false,
189
+ previousOffset: null,
190
+ totalSize: itemsWithMetadata.reduce((sum, item) => sum + (item.size || 0), 0),
191
+ averageSize: Math.round(itemsWithMetadata.reduce((sum, item) => sum + (item.size || 0), 0) / result.items.length),
192
+ defaultsApplied: { limit: true, sort: true },
193
+ truncated: false,
194
+ truncatedCount: 0,
195
+ },
196
+ };
197
+ const fullResponseJson = JSON.stringify(response, null, 2);
198
+ const finalTokens = estimateTokens(fullResponseJson);
199
+ console.log(`Final response tokens: ${finalTokens}`);
200
+ // Verify the fix works
201
+ expect(limit).toBe(30); // Our new default when includeMetadata is true
202
+ expect(result.items.length).toBeLessThanOrEqual(30);
203
+ expect(finalTokens).toBeLessThan(15000); // Our new conservative TOKEN_LIMIT
204
+ expect(finalTokens).toBeLessThan(25000); // MCP's actual limit
205
+ console.log('✅ Fix verified: Response stays well under token limit');
206
+ });
207
+ it('should still allow explicit higher limits but truncate if needed', () => {
208
+ // User explicitly requests more items
209
+ const args = {
210
+ sessionId: testSessionId,
211
+ includeMetadata: true,
212
+ limit: 100, // Explicitly requesting many items
213
+ };
214
+ const result = repositories.contexts.queryEnhanced({
215
+ sessionId: testSessionId,
216
+ includeMetadata: true,
217
+ limit: args.limit,
218
+ });
219
+ // With explicit limit, we get what was requested (up to 100)
220
+ expect(result.items.length).toBeLessThanOrEqual(100);
221
+ // But in the handler, if this exceeds tokens, it would be truncated
222
+ const itemsWithMetadata = result.items.map(item => ({
223
+ key: item.key,
224
+ value: item.value,
225
+ category: item.category,
226
+ priority: item.priority,
227
+ channel: item.channel,
228
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
229
+ size: item.size || calculateSize(item.value),
230
+ created_at: item.created_at,
231
+ updated_at: item.updated_at,
232
+ }));
233
+ const responseJson = JSON.stringify({ items: itemsWithMetadata }, null, 2);
234
+ const tokens = estimateTokens(responseJson);
235
+ console.log(`With explicit limit=100: ${result.items.length} items, ${tokens} tokens`);
236
+ // This would trigger truncation in the handler
237
+ if (tokens > 15000) {
238
+ console.log('⚠️ Would trigger truncation in handler');
239
+ }
240
+ });
241
+ });
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ // Test to validate the fix for issue #24
3
+ // Tests the actual flow through index.ts handler
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ const globals_1 = require("@jest/globals");
6
+ // Mock the functions used in index.ts for context_get
7
+ function estimateTokens(text) {
8
+ return Math.ceil(text.length / 4);
9
+ }
10
+ function calculateSize(value) {
11
+ return Buffer.byteLength(value, 'utf8');
12
+ }
13
+ function calculateResponseMetrics(items) {
14
+ let totalSize = 0;
15
+ for (const item of items) {
16
+ const itemSize = item.size || calculateSize(item.value);
17
+ totalSize += itemSize;
18
+ }
19
+ // Convert to JSON string to get actual response size
20
+ const jsonString = JSON.stringify(items);
21
+ const estimatedTokens = estimateTokens(jsonString);
22
+ const averageSize = items.length > 0 ? Math.round(totalSize / items.length) : 0;
23
+ return { totalSize, estimatedTokens, averageSize };
24
+ }
25
+ (0, globals_1.describe)('Issue #24 Fix Validation', () => {
26
+ (0, globals_1.it)('should correctly calculate tokens for response with metadata', () => {
27
+ // Create sample items as they would come from the database
28
+ const dbItems = [];
29
+ for (let i = 0; i < 100; i++) {
30
+ dbItems.push({
31
+ id: `id-${i}`,
32
+ session_id: 'test-session',
33
+ key: `test_item_${i}`,
34
+ value: `This is test content that is moderately long to simulate real data. `.repeat(3),
35
+ category: 'task',
36
+ priority: 'high',
37
+ channel: 'test',
38
+ metadata: JSON.stringify({ index: i }),
39
+ size: 200,
40
+ created_at: '2025-01-20T10:00:00Z',
41
+ updated_at: '2025-01-20T10:00:00Z',
42
+ });
43
+ }
44
+ // Test without metadata (original calculation)
45
+ const metricsWithoutMetadata = calculateResponseMetrics(dbItems);
46
+ console.log('Without metadata - Items:', dbItems.length, 'Tokens:', metricsWithoutMetadata.estimatedTokens);
47
+ // Test with metadata (as per the fix)
48
+ const itemsWithMetadata = dbItems.map(item => ({
49
+ key: item.key,
50
+ value: item.value,
51
+ category: item.category,
52
+ priority: item.priority,
53
+ channel: item.channel,
54
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
55
+ size: item.size || calculateSize(item.value),
56
+ created_at: item.created_at,
57
+ updated_at: item.updated_at,
58
+ }));
59
+ const metricsWithMetadata = calculateResponseMetrics(itemsWithMetadata);
60
+ console.log('With metadata - Items:', itemsWithMetadata.length, 'Tokens:', metricsWithMetadata.estimatedTokens);
61
+ // The metadata version might have fewer tokens due to JSON parsing
62
+ // but the overall response structure adds overhead
63
+ // Build full response structure as the handler does
64
+ const fullResponse = {
65
+ items: itemsWithMetadata,
66
+ pagination: {
67
+ total: 100,
68
+ returned: 100,
69
+ offset: 0,
70
+ hasMore: false,
71
+ nextOffset: null,
72
+ totalCount: 100,
73
+ page: 1,
74
+ pageSize: 100,
75
+ totalPages: 1,
76
+ hasNextPage: false,
77
+ hasPreviousPage: false,
78
+ previousOffset: null,
79
+ totalSize: metricsWithMetadata.totalSize,
80
+ averageSize: metricsWithMetadata.averageSize,
81
+ defaultsApplied: { limit: true, sort: true },
82
+ truncated: false,
83
+ truncatedCount: 0,
84
+ },
85
+ };
86
+ const finalResponseJson = JSON.stringify(fullResponse, null, 2);
87
+ const finalTokens = estimateTokens(finalResponseJson);
88
+ console.log('Final response - Size:', finalResponseJson.length, 'Tokens:', finalTokens);
89
+ // This demonstrates the issue: the final response can be much larger
90
+ // than what calculateResponseMetrics estimates
91
+ console.log('Token difference:', finalTokens - metricsWithMetadata.estimatedTokens);
92
+ // With 100 items and metadata, we should be approaching or exceeding limits
93
+ if (finalTokens > 18000) {
94
+ console.log('WARNING: Response exceeds safe token limit!');
95
+ }
96
+ });
97
+ (0, globals_1.it)('should demonstrate that 50 items with metadata stays under limit', () => {
98
+ // Create sample items
99
+ const dbItems = [];
100
+ for (let i = 0; i < 50; i++) {
101
+ dbItems.push({
102
+ id: `id-${i}`,
103
+ session_id: 'test-session',
104
+ key: `test_item_${i}`,
105
+ value: `This is test content that is moderately long to simulate real data. `.repeat(3),
106
+ category: 'task',
107
+ priority: 'high',
108
+ channel: 'test',
109
+ metadata: JSON.stringify({ index: i }),
110
+ size: 200,
111
+ created_at: '2025-01-20T10:00:00Z',
112
+ updated_at: '2025-01-20T10:00:00Z',
113
+ });
114
+ }
115
+ // Transform with metadata
116
+ const itemsWithMetadata = dbItems.map(item => ({
117
+ key: item.key,
118
+ value: item.value,
119
+ category: item.category,
120
+ priority: item.priority,
121
+ channel: item.channel,
122
+ metadata: item.metadata ? JSON.parse(item.metadata) : null,
123
+ size: item.size || calculateSize(item.value),
124
+ created_at: item.created_at,
125
+ updated_at: item.updated_at,
126
+ }));
127
+ const metrics = calculateResponseMetrics(itemsWithMetadata);
128
+ // Build full response
129
+ const fullResponse = {
130
+ items: itemsWithMetadata,
131
+ pagination: {
132
+ total: 50,
133
+ returned: 50,
134
+ offset: 0,
135
+ hasMore: false,
136
+ nextOffset: null,
137
+ totalCount: 50,
138
+ page: 1,
139
+ pageSize: 50,
140
+ totalPages: 1,
141
+ hasNextPage: false,
142
+ hasPreviousPage: false,
143
+ previousOffset: null,
144
+ totalSize: metrics.totalSize,
145
+ averageSize: metrics.averageSize,
146
+ defaultsApplied: { limit: true, sort: true },
147
+ truncated: false,
148
+ truncatedCount: 0,
149
+ },
150
+ };
151
+ const finalResponseJson = JSON.stringify(fullResponse, null, 2);
152
+ const finalTokens = estimateTokens(finalResponseJson);
153
+ console.log('50 items with metadata - Tokens:', finalTokens);
154
+ // 50 items should be safe
155
+ (0, globals_1.expect)(finalTokens).toBeLessThan(18000);
156
+ (0, globals_1.expect)(finalTokens).toBeLessThan(25000); // Well under MCP limit
157
+ });
158
+ });