memory-journal-mcp 4.3.0 → 4.4.0
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/.dockerignore +131 -122
- package/.gitattributes +29 -0
- package/.github/workflows/docker-publish.yml +1 -1
- package/.github/workflows/lint-and-test.yml +1 -2
- package/.github/workflows/secrets-scanning.yml +0 -1
- package/.github/workflows/security-update.yml +6 -6
- package/.vscode/settings.json +17 -15
- package/CHANGELOG.md +1065 -11
- package/DOCKER_README.md +51 -33
- package/Dockerfile +14 -12
- package/README.md +68 -33
- package/SECURITY.md +225 -220
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +70 -26
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -0
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +6 -0
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +51 -10
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +143 -43
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +7 -1
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts +74 -2
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +508 -7
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/index.js +1 -0
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +257 -13
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +595 -8
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/server/McpServer.d.ts +2 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +69 -26
- package/dist/server/McpServer.js.map +1 -1
- package/dist/types/index.d.ts +97 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/progress-utils.d.ts +18 -3
- package/dist/utils/progress-utils.d.ts.map +1 -1
- package/dist/utils/progress-utils.js.map +1 -1
- package/dist/utils/security-utils.d.ts +91 -0
- package/dist/utils/security-utils.d.ts.map +1 -0
- package/dist/utils/security-utils.js +184 -0
- package/dist/utils/security-utils.js.map +1 -0
- package/dist/vector/VectorSearchManager.d.ts +2 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +100 -34
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +46 -37
- package/mcp-config-example.json +0 -2
- package/package.json +21 -14
- package/releases/v4.3.1.md +69 -0
- package/releases/v4.4.0.md +120 -0
- package/server.json +3 -3
- package/src/cli.ts +11 -0
- package/src/constants/ServerInstructions.ts +70 -26
- package/src/constants/icons.ts +7 -0
- package/src/database/SqliteAdapter.ts +165 -44
- package/src/filtering/ToolFilter.ts +7 -1
- package/src/github/GitHubIntegration.ts +588 -8
- package/src/handlers/prompts/index.ts +1 -0
- package/src/handlers/resources/index.ts +318 -12
- package/src/handlers/tools/index.ts +686 -13
- package/src/server/McpServer.ts +79 -37
- package/src/types/index.ts +98 -0
- package/src/utils/logger.ts +10 -1
- package/src/utils/progress-utils.ts +17 -6
- package/src/utils/security-utils.ts +205 -0
- package/src/vector/VectorSearchManager.ts +110 -39
- package/tests/constants/icons.test.ts +102 -0
- package/tests/constants/server-instructions.test.ts +549 -0
- package/tests/database/sqlite-adapter.bench.ts +63 -0
- package/tests/database/sqlite-adapter.test.ts +555 -0
- package/tests/filtering/tool-filter.test.ts +266 -0
- package/tests/github/github-integration.test.ts +1024 -0
- package/tests/handlers/github-resource-handlers.test.ts +473 -0
- package/tests/handlers/github-tool-handlers.test.ts +556 -0
- package/tests/handlers/prompt-handlers.test.ts +91 -0
- package/tests/handlers/resource-handlers.test.ts +339 -0
- package/tests/handlers/tool-handlers.test.ts +497 -0
- package/tests/handlers/vector-tool-handlers.test.ts +238 -0
- package/tests/security/sql-injection.test.ts +347 -0
- package/tests/server/mcp-server.bench.ts +55 -0
- package/tests/server/mcp-server.test.ts +675 -0
- package/tests/utils/logger.test.ts +180 -0
- package/tests/utils/mcp-logger.test.ts +212 -0
- package/tests/utils/progress-utils.test.ts +156 -0
- package/tests/utils/security-utils.test.ts +82 -0
- package/tests/vector/vector-search-manager.test.ts +335 -0
- package/tests/vector/vector-search.bench.ts +53 -0
- package/vitest.config.ts +15 -0
- package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
- package/.github/workflows/dependabot-auto-merge.yml +0 -42
|
@@ -20,6 +20,12 @@ const DEFAULT_MODEL = 'Xenova/all-MiniLM-L6-v2'
|
|
|
20
20
|
/** Embedding dimensions for all-MiniLM-L6-v2 */
|
|
21
21
|
const EMBEDDING_DIMENSIONS = 384
|
|
22
22
|
|
|
23
|
+
/** Number of entries to embed concurrently during rebuild */
|
|
24
|
+
const REBUILD_BATCH_SIZE = 5
|
|
25
|
+
|
|
26
|
+
/** Number of entries to fetch per page during rebuild */
|
|
27
|
+
const REBUILD_PAGE_SIZE = 200
|
|
28
|
+
|
|
23
29
|
/** Search result with similarity score */
|
|
24
30
|
export interface SemanticSearchResult {
|
|
25
31
|
entryId: number
|
|
@@ -225,7 +231,8 @@ export class VectorSearchManager {
|
|
|
225
231
|
}
|
|
226
232
|
|
|
227
233
|
/**
|
|
228
|
-
* Rebuild index from database entries
|
|
234
|
+
* Rebuild index from database entries.
|
|
235
|
+
* Uses paginated fetching and parallel batch embedding for performance.
|
|
229
236
|
* @param db - Database adapter
|
|
230
237
|
* @param progress - Optional progress context for notifications
|
|
231
238
|
*/
|
|
@@ -240,66 +247,130 @@ export class VectorSearchManager {
|
|
|
240
247
|
|
|
241
248
|
logger.info('Rebuilding vector index from database...', { module: 'VectorSearch' })
|
|
242
249
|
|
|
243
|
-
// Step 1: Get
|
|
244
|
-
const
|
|
245
|
-
const dbIds = new Set(
|
|
250
|
+
// Step 1: Get total entry count and build ID set for orphan detection
|
|
251
|
+
const totalEntries = db.getActiveEntryCount()
|
|
252
|
+
const dbIds = new Set<string>()
|
|
253
|
+
|
|
254
|
+
// Collect all active entry IDs via pagination (avoids loading all content at once)
|
|
255
|
+
for (let offset = 0; offset < totalEntries; offset += REBUILD_PAGE_SIZE) {
|
|
256
|
+
const page = db.getEntriesPage(offset, REBUILD_PAGE_SIZE)
|
|
257
|
+
for (const entry of page) {
|
|
258
|
+
dbIds.add(String(entry.id))
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Step 2: Clean up existing index items
|
|
263
|
+
// If the index is corrupted (e.g., from a process kill), recreate it
|
|
264
|
+
try {
|
|
265
|
+
const indexItems = await this.index.listItems()
|
|
266
|
+
let orphansRemoved = 0
|
|
267
|
+
for (const item of indexItems) {
|
|
268
|
+
if (!dbIds.has(item.id)) {
|
|
269
|
+
try {
|
|
270
|
+
await this.index.deleteItem(item.id)
|
|
271
|
+
orphansRemoved++
|
|
272
|
+
} catch {
|
|
273
|
+
// Ignore errors during cleanup
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (orphansRemoved > 0) {
|
|
278
|
+
logger.info(`Cleaned up ${String(orphansRemoved)} orphaned vector entries`, {
|
|
279
|
+
module: 'VectorSearch',
|
|
280
|
+
})
|
|
281
|
+
}
|
|
246
282
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
for (const item of indexItems) {
|
|
252
|
-
if (!dbIds.has(item.id)) {
|
|
283
|
+
// Step 3: Delete all remaining items to prepare for clean re-index
|
|
284
|
+
// This avoids the double-delete overhead of calling addEntry (upsert) per item
|
|
285
|
+
const remainingItems = await this.index.listItems()
|
|
286
|
+
for (const item of remainingItems) {
|
|
253
287
|
try {
|
|
254
288
|
await this.index.deleteItem(item.id)
|
|
255
|
-
orphansRemoved++
|
|
256
289
|
} catch {
|
|
257
|
-
// Ignore
|
|
290
|
+
// Ignore
|
|
258
291
|
}
|
|
259
292
|
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
logger.
|
|
293
|
+
} catch (indexError) {
|
|
294
|
+
// Index files are corrupted — recreate from scratch
|
|
295
|
+
logger.warning('Vector index corrupted, recreating...', {
|
|
263
296
|
module: 'VectorSearch',
|
|
297
|
+
error: indexError instanceof Error ? indexError.message : String(indexError),
|
|
264
298
|
})
|
|
299
|
+
// Delete and recreate the vectra index directory
|
|
300
|
+
if (fs.existsSync(this.indexPath)) {
|
|
301
|
+
fs.rmSync(this.indexPath, { recursive: true, force: true })
|
|
302
|
+
fs.mkdirSync(this.indexPath, { recursive: true })
|
|
303
|
+
}
|
|
304
|
+
this.index = new LocalIndex(this.indexPath)
|
|
305
|
+
await this.index.createIndex()
|
|
306
|
+
logger.info('Recreated vector index after corruption', { module: 'VectorSearch' })
|
|
265
307
|
}
|
|
266
308
|
|
|
267
|
-
// Step
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
await sendProgress(progress, 0, total, 'Starting vector index rebuild...')
|
|
309
|
+
// Step 4: Re-index all entries using paginated fetch
|
|
310
|
+
// Embeddings are generated in parallel batches (CPU-bound, safe),
|
|
311
|
+
// but vectra insertions are sequential (file I/O, not concurrency-safe)
|
|
312
|
+
await sendProgress(progress, 0, totalEntries, 'Starting vector index rebuild...')
|
|
272
313
|
|
|
273
314
|
let indexed = 0
|
|
274
|
-
for (
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
315
|
+
for (let offset = 0; offset < totalEntries; offset += REBUILD_PAGE_SIZE) {
|
|
316
|
+
const page = db.getEntriesPage(offset, REBUILD_PAGE_SIZE)
|
|
317
|
+
|
|
318
|
+
// Generate embeddings in parallel batches
|
|
319
|
+
for (let i = 0; i < page.length; i += REBUILD_BATCH_SIZE) {
|
|
320
|
+
const batch = page.slice(i, i + REBUILD_BATCH_SIZE)
|
|
321
|
+
|
|
322
|
+
// Parallel embedding generation
|
|
323
|
+
const embeddings = await Promise.all(
|
|
324
|
+
batch.map(async (entry) => {
|
|
325
|
+
try {
|
|
326
|
+
return { entry, embedding: await this.generateEmbedding(entry.content) }
|
|
327
|
+
} catch {
|
|
328
|
+
return { entry, embedding: null }
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
)
|
|
281
332
|
|
|
282
|
-
|
|
283
|
-
|
|
333
|
+
// Sequential vectra insertion (file I/O not concurrency-safe)
|
|
334
|
+
for (const { entry, embedding } of embeddings) {
|
|
335
|
+
if (embedding !== null) {
|
|
336
|
+
try {
|
|
337
|
+
await this.index.insertItem({
|
|
338
|
+
id: String(entry.id),
|
|
339
|
+
vector: embedding,
|
|
340
|
+
metadata: {
|
|
341
|
+
entryId: entry.id,
|
|
342
|
+
contentPreview: entry.content.slice(0, 100),
|
|
343
|
+
},
|
|
344
|
+
})
|
|
345
|
+
indexed++
|
|
346
|
+
} catch (error) {
|
|
347
|
+
logger.error('Failed to insert entry into vector index', {
|
|
348
|
+
module: 'VectorSearch',
|
|
349
|
+
entityId: entry.id,
|
|
350
|
+
error: error instanceof Error ? error.message : String(error),
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
284
355
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
356
|
+
// Report progress every 10 entries to avoid flooding
|
|
357
|
+
if (indexed % 10 === 0 || indexed === totalEntries) {
|
|
358
|
+
await sendProgress(
|
|
359
|
+
progress,
|
|
360
|
+
indexed,
|
|
361
|
+
totalEntries,
|
|
362
|
+
`Indexed ${String(indexed)} of ${String(totalEntries)} entries`
|
|
363
|
+
)
|
|
364
|
+
}
|
|
293
365
|
}
|
|
294
366
|
}
|
|
295
367
|
|
|
296
368
|
// Force index to refresh by re-listing items
|
|
297
369
|
// This ensures the internal query structures are updated and ready for search
|
|
298
|
-
// The 100ms delay was insufficient because it didn't refresh the internal state
|
|
299
370
|
await this.index.listItems()
|
|
300
371
|
|
|
301
372
|
// Final progress
|
|
302
|
-
await sendProgress(progress, indexed,
|
|
373
|
+
await sendProgress(progress, indexed, totalEntries, 'Vector index rebuild complete')
|
|
303
374
|
|
|
304
375
|
logger.info(`Rebuilt vector index with ${String(indexed)} entries`, {
|
|
305
376
|
module: 'VectorSearch',
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icons Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests icon constants and getToolIcon function.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest'
|
|
8
|
+
import {
|
|
9
|
+
ICON_CORE,
|
|
10
|
+
ICON_SEARCH,
|
|
11
|
+
ICON_ANALYTICS,
|
|
12
|
+
ICON_RELATIONSHIPS,
|
|
13
|
+
ICON_EXPORT,
|
|
14
|
+
ICON_ADMIN,
|
|
15
|
+
ICON_GITHUB,
|
|
16
|
+
ICON_BACKUP,
|
|
17
|
+
ICON_BRIEFING,
|
|
18
|
+
ICON_CLOCK,
|
|
19
|
+
ICON_TAG,
|
|
20
|
+
ICON_TEAM,
|
|
21
|
+
ICON_STAR,
|
|
22
|
+
ICON_ISSUE,
|
|
23
|
+
ICON_PR,
|
|
24
|
+
ICON_PROMPT,
|
|
25
|
+
getToolIcon,
|
|
26
|
+
} from '../../src/constants/icons.js'
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Tool Group Icons
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
describe('Tool group icons', () => {
|
|
33
|
+
const toolIcons = [
|
|
34
|
+
{ name: 'ICON_CORE', icon: ICON_CORE },
|
|
35
|
+
{ name: 'ICON_SEARCH', icon: ICON_SEARCH },
|
|
36
|
+
{ name: 'ICON_ANALYTICS', icon: ICON_ANALYTICS },
|
|
37
|
+
{ name: 'ICON_RELATIONSHIPS', icon: ICON_RELATIONSHIPS },
|
|
38
|
+
{ name: 'ICON_EXPORT', icon: ICON_EXPORT },
|
|
39
|
+
{ name: 'ICON_ADMIN', icon: ICON_ADMIN },
|
|
40
|
+
{ name: 'ICON_GITHUB', icon: ICON_GITHUB },
|
|
41
|
+
{ name: 'ICON_BACKUP', icon: ICON_BACKUP },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
it.each(toolIcons)('$name should have valid SVG data URI', ({ icon }) => {
|
|
45
|
+
expect(icon.src).toMatch(/^data:image\/svg\+xml/)
|
|
46
|
+
expect(icon.mimeType).toBe('image/svg+xml')
|
|
47
|
+
expect(icon.sizes).toContain('24x24')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Resource Icons
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
describe('Resource icons', () => {
|
|
56
|
+
const resourceIcons = [
|
|
57
|
+
{ name: 'ICON_BRIEFING', icon: ICON_BRIEFING },
|
|
58
|
+
{ name: 'ICON_CLOCK', icon: ICON_CLOCK },
|
|
59
|
+
{ name: 'ICON_TAG', icon: ICON_TAG },
|
|
60
|
+
{ name: 'ICON_TEAM', icon: ICON_TEAM },
|
|
61
|
+
{ name: 'ICON_STAR', icon: ICON_STAR },
|
|
62
|
+
{ name: 'ICON_ISSUE', icon: ICON_ISSUE },
|
|
63
|
+
{ name: 'ICON_PR', icon: ICON_PR },
|
|
64
|
+
{ name: 'ICON_PROMPT', icon: ICON_PROMPT },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
it.each(resourceIcons)('$name should have valid SVG data URI', ({ icon }) => {
|
|
68
|
+
expect(icon.src).toMatch(/^data:image\/svg\+xml/)
|
|
69
|
+
expect(icon.mimeType).toBe('image/svg+xml')
|
|
70
|
+
expect(icon.sizes).toContain('24x24')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// getToolIcon
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
describe('getToolIcon', () => {
|
|
79
|
+
it('should return icon array for valid groups', () => {
|
|
80
|
+
const groups = [
|
|
81
|
+
'core',
|
|
82
|
+
'search',
|
|
83
|
+
'analytics',
|
|
84
|
+
'relationships',
|
|
85
|
+
'export',
|
|
86
|
+
'admin',
|
|
87
|
+
'github',
|
|
88
|
+
'backup',
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
for (const group of groups) {
|
|
92
|
+
const icons = getToolIcon(group)
|
|
93
|
+
expect(icons).toBeDefined()
|
|
94
|
+
expect(Array.isArray(icons)).toBe(true)
|
|
95
|
+
expect(icons!.length).toBeGreaterThan(0)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should return undefined for unknown group', () => {
|
|
100
|
+
expect(getToolIcon('nonexistent')).toBeUndefined()
|
|
101
|
+
})
|
|
102
|
+
})
|