memory-journal-mcp 4.3.1 → 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 +12 -11
- package/README.md +68 -33
- package/SECURITY.md +225 -158
- 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 +140 -31
- 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 +590 -7
- 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/security-utils.d.ts +8 -0
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +32 -0
- package/dist/utils/security-utils.js.map +1 -1
- 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 +12 -11
- 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 +162 -32
- 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 +681 -12
- package/src/server/McpServer.ts +79 -37
- package/src/types/index.ts +98 -0
- package/src/utils/logger.ts +10 -1
- package/src/utils/security-utils.ts +35 -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/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/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
- package/.github/workflows/dependabot-auto-merge.yml +0 -42
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Tool Handler Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests vector-dependent tools (semantic_search, rebuild_vector_index,
|
|
5
|
+
* add_to_vector_index, get_vector_index_stats) using a mock VectorSearchManager.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'
|
|
9
|
+
import { callTool } from '../../src/handlers/tools/index.js'
|
|
10
|
+
import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
|
|
11
|
+
import type { VectorSearchManager } from '../../src/vector/VectorSearchManager.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a mock VectorSearchManager.
|
|
15
|
+
*/
|
|
16
|
+
function createMockVector(overrides: Partial<Record<string, unknown>> = {}): VectorSearchManager {
|
|
17
|
+
const defaults = {
|
|
18
|
+
isInitialized: vi.fn().mockReturnValue(true),
|
|
19
|
+
initialize: vi.fn().mockResolvedValue(undefined),
|
|
20
|
+
search: vi.fn().mockResolvedValue([]),
|
|
21
|
+
addEntry: vi.fn().mockResolvedValue(true),
|
|
22
|
+
removeEntry: vi.fn().mockResolvedValue(true),
|
|
23
|
+
rebuildIndex: vi.fn().mockResolvedValue(5),
|
|
24
|
+
getStats: vi.fn().mockResolvedValue({
|
|
25
|
+
itemCount: 10,
|
|
26
|
+
modelName: 'Xenova/all-MiniLM-L6-v2',
|
|
27
|
+
dimensions: 384,
|
|
28
|
+
}),
|
|
29
|
+
generateEmbedding: vi.fn().mockResolvedValue(new Array(384).fill(0)),
|
|
30
|
+
}
|
|
31
|
+
return { ...defaults, ...overrides } as unknown as VectorSearchManager
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('Vector Tool Handlers', () => {
|
|
35
|
+
let db: SqliteAdapter
|
|
36
|
+
const testDbPath = './test-vector-tools.db'
|
|
37
|
+
let entryId: number
|
|
38
|
+
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
db = new SqliteAdapter(testDbPath)
|
|
41
|
+
await db.initialize()
|
|
42
|
+
// Create an entry for testing
|
|
43
|
+
const entry = db.createEntry({
|
|
44
|
+
content: 'Test entry for vector tools',
|
|
45
|
+
entryType: 'personal_reflection',
|
|
46
|
+
tags: ['test'],
|
|
47
|
+
})
|
|
48
|
+
entryId = entry.id
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
afterAll(() => {
|
|
52
|
+
db.close()
|
|
53
|
+
try {
|
|
54
|
+
const fs = require('node:fs')
|
|
55
|
+
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath)
|
|
56
|
+
} catch {
|
|
57
|
+
// Ignore cleanup errors
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// ========================================================================
|
|
62
|
+
// semantic_search
|
|
63
|
+
// ========================================================================
|
|
64
|
+
|
|
65
|
+
describe('semantic_search', () => {
|
|
66
|
+
it('should return results when vector manager has matches', async () => {
|
|
67
|
+
const vectorManager = createMockVector({
|
|
68
|
+
search: vi.fn().mockResolvedValue([{ entryId, score: 0.85 }]),
|
|
69
|
+
getStats: vi.fn().mockResolvedValue({ itemCount: 10 }),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const result = (await callTool(
|
|
73
|
+
'semantic_search',
|
|
74
|
+
{ query: 'test query' },
|
|
75
|
+
db,
|
|
76
|
+
vectorManager
|
|
77
|
+
)) as { query: string; entries: unknown[]; count: number }
|
|
78
|
+
|
|
79
|
+
expect(result.query).toBe('test query')
|
|
80
|
+
expect(result.count).toBe(1)
|
|
81
|
+
expect(result.entries).toHaveLength(1)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should return empty with hint when index is empty', async () => {
|
|
85
|
+
const vectorManager = createMockVector({
|
|
86
|
+
search: vi.fn().mockResolvedValue([]),
|
|
87
|
+
getStats: vi.fn().mockResolvedValue({ itemCount: 0 }),
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const result = (await callTool(
|
|
91
|
+
'semantic_search',
|
|
92
|
+
{ query: 'anything' },
|
|
93
|
+
db,
|
|
94
|
+
vectorManager
|
|
95
|
+
)) as { entries: unknown[]; count: number; hint: string }
|
|
96
|
+
|
|
97
|
+
expect(result.count).toBe(0)
|
|
98
|
+
expect(result.hint).toContain('rebuild_vector_index')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should return hint when no matches above threshold', async () => {
|
|
102
|
+
const vectorManager = createMockVector({
|
|
103
|
+
search: vi.fn().mockResolvedValue([]),
|
|
104
|
+
getStats: vi.fn().mockResolvedValue({ itemCount: 10 }),
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const result = (await callTool(
|
|
108
|
+
'semantic_search',
|
|
109
|
+
{ query: 'nothing matches' },
|
|
110
|
+
db,
|
|
111
|
+
vectorManager
|
|
112
|
+
)) as { count: number; hint: string }
|
|
113
|
+
|
|
114
|
+
expect(result.count).toBe(0)
|
|
115
|
+
expect(result.hint).toContain('similarity_threshold')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should suppress hint when hint_on_empty is false', async () => {
|
|
119
|
+
const vectorManager = createMockVector({
|
|
120
|
+
search: vi.fn().mockResolvedValue([]),
|
|
121
|
+
getStats: vi.fn().mockResolvedValue({ itemCount: 0 }),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const result = (await callTool(
|
|
125
|
+
'semantic_search',
|
|
126
|
+
{ query: 'query', hint_on_empty: false },
|
|
127
|
+
db,
|
|
128
|
+
vectorManager
|
|
129
|
+
)) as { count: number; hint?: string }
|
|
130
|
+
|
|
131
|
+
expect(result.count).toBe(0)
|
|
132
|
+
expect(result.hint).toBeUndefined()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should return error when no vector manager', async () => {
|
|
136
|
+
const result = (await callTool('semantic_search', { query: 'query' }, db)) as {
|
|
137
|
+
error: string
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
expect(result.error).toContain('not initialized')
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// ========================================================================
|
|
145
|
+
// rebuild_vector_index
|
|
146
|
+
// ========================================================================
|
|
147
|
+
|
|
148
|
+
describe('rebuild_vector_index', () => {
|
|
149
|
+
it('should rebuild and return count', async () => {
|
|
150
|
+
const vectorManager = createMockVector()
|
|
151
|
+
|
|
152
|
+
const result = (await callTool('rebuild_vector_index', {}, db, vectorManager)) as {
|
|
153
|
+
success: boolean
|
|
154
|
+
entriesIndexed: number
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
expect(result.success).toBe(true)
|
|
158
|
+
expect(result.entriesIndexed).toBe(5)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should return error when no vector manager', async () => {
|
|
162
|
+
const result = (await callTool('rebuild_vector_index', {}, db)) as { error: string }
|
|
163
|
+
|
|
164
|
+
expect(result.error).toContain('not available')
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// ========================================================================
|
|
169
|
+
// add_to_vector_index
|
|
170
|
+
// ========================================================================
|
|
171
|
+
|
|
172
|
+
describe('add_to_vector_index', () => {
|
|
173
|
+
it('should add entry to index', async () => {
|
|
174
|
+
const vectorManager = createMockVector()
|
|
175
|
+
|
|
176
|
+
const result = (await callTool(
|
|
177
|
+
'add_to_vector_index',
|
|
178
|
+
{ entry_id: entryId },
|
|
179
|
+
db,
|
|
180
|
+
vectorManager
|
|
181
|
+
)) as { success: boolean; entryId: number }
|
|
182
|
+
|
|
183
|
+
expect(result.success).toBe(true)
|
|
184
|
+
expect(result.entryId).toBe(entryId)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('should return error for nonexistent entry', async () => {
|
|
188
|
+
const vectorManager = createMockVector()
|
|
189
|
+
|
|
190
|
+
const result = (await callTool(
|
|
191
|
+
'add_to_vector_index',
|
|
192
|
+
{ entry_id: 99999 },
|
|
193
|
+
db,
|
|
194
|
+
vectorManager
|
|
195
|
+
)) as { error: string }
|
|
196
|
+
|
|
197
|
+
expect(result.error).toContain('not found')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should return error when no vector manager', async () => {
|
|
201
|
+
const result = (await callTool('add_to_vector_index', { entry_id: 1 }, db)) as {
|
|
202
|
+
error: string
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
expect(result.error).toContain('not available')
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// ========================================================================
|
|
210
|
+
// get_vector_index_stats
|
|
211
|
+
// ========================================================================
|
|
212
|
+
|
|
213
|
+
describe('get_vector_index_stats', () => {
|
|
214
|
+
it('should return stats', async () => {
|
|
215
|
+
const vectorManager = createMockVector()
|
|
216
|
+
|
|
217
|
+
const result = (await callTool('get_vector_index_stats', {}, db, vectorManager)) as {
|
|
218
|
+
available: boolean
|
|
219
|
+
itemCount: number
|
|
220
|
+
modelName: string
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
expect(result.available).toBe(true)
|
|
224
|
+
expect(result.itemCount).toBe(10)
|
|
225
|
+
expect(result.modelName).toBe('Xenova/all-MiniLM-L6-v2')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should return unavailable when no vector manager', async () => {
|
|
229
|
+
const result = (await callTool('get_vector_index_stats', {}, db)) as {
|
|
230
|
+
available: boolean
|
|
231
|
+
error: string
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
expect(result.available).toBe(false)
|
|
235
|
+
expect(result.error).toContain('not available')
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { bench, beforeAll, afterAll } from 'vitest'
|
|
2
|
+
import { getTools, callTool } from '../../src/handlers/tools/index.js'
|
|
3
|
+
import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
|
|
4
|
+
import * as fs from 'node:fs'
|
|
5
|
+
import * as path from 'node:path'
|
|
6
|
+
|
|
7
|
+
let db: SqliteAdapter
|
|
8
|
+
const testDbPath = path.join(process.cwd(), 'benchmark-server-tools.db')
|
|
9
|
+
|
|
10
|
+
beforeAll(async () => {
|
|
11
|
+
db = new SqliteAdapter(testDbPath)
|
|
12
|
+
await db.initialize()
|
|
13
|
+
|
|
14
|
+
// Setup initial data
|
|
15
|
+
for (let i = 0; i < 1000; i++) {
|
|
16
|
+
db.createEntry({
|
|
17
|
+
content: `Server benchmark tool entry ${i} ${Math.random().toString(36)}`,
|
|
18
|
+
entryType: 'decision',
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
db.close()
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(testDbPath)) {
|
|
27
|
+
fs.unlinkSync(testDbPath)
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// Ignore signup errors
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
bench('getTools', () => {
|
|
35
|
+
getTools(db, null)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
bench('callTool create_entry', async () => {
|
|
39
|
+
await callTool(
|
|
40
|
+
'create_entry',
|
|
41
|
+
{
|
|
42
|
+
content: 'Bench creation entry text over a tool call',
|
|
43
|
+
entry_type: 'personal_reflection',
|
|
44
|
+
},
|
|
45
|
+
db
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
bench('callTool get_recent_entries', async () => {
|
|
50
|
+
await callTool('get_recent_entries', { limit: 50 }, db)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
bench('callTool search_entries', async () => {
|
|
54
|
+
await callTool('search_entries', { query: 'benchmark', limit: 20 }, db)
|
|
55
|
+
})
|