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.
Files changed (103) hide show
  1. package/.dockerignore +131 -122
  2. package/.gitattributes +29 -0
  3. package/.github/workflows/docker-publish.yml +1 -1
  4. package/.github/workflows/lint-and-test.yml +1 -2
  5. package/.github/workflows/secrets-scanning.yml +0 -1
  6. package/.github/workflows/security-update.yml +6 -6
  7. package/.vscode/settings.json +17 -15
  8. package/CHANGELOG.md +1065 -11
  9. package/DOCKER_README.md +51 -33
  10. package/Dockerfile +12 -11
  11. package/README.md +68 -33
  12. package/SECURITY.md +225 -158
  13. package/dist/cli.js +7 -0
  14. package/dist/cli.js.map +1 -1
  15. package/dist/constants/ServerInstructions.d.ts +1 -1
  16. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  17. package/dist/constants/ServerInstructions.js +70 -26
  18. package/dist/constants/ServerInstructions.js.map +1 -1
  19. package/dist/constants/icons.d.ts +2 -0
  20. package/dist/constants/icons.d.ts.map +1 -1
  21. package/dist/constants/icons.js +6 -0
  22. package/dist/constants/icons.js.map +1 -1
  23. package/dist/database/SqliteAdapter.d.ts +51 -10
  24. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  25. package/dist/database/SqliteAdapter.js +140 -31
  26. package/dist/database/SqliteAdapter.js.map +1 -1
  27. package/dist/filtering/ToolFilter.d.ts +1 -1
  28. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  29. package/dist/filtering/ToolFilter.js +7 -1
  30. package/dist/filtering/ToolFilter.js.map +1 -1
  31. package/dist/github/GitHubIntegration.d.ts +74 -2
  32. package/dist/github/GitHubIntegration.d.ts.map +1 -1
  33. package/dist/github/GitHubIntegration.js +508 -7
  34. package/dist/github/GitHubIntegration.js.map +1 -1
  35. package/dist/handlers/prompts/index.js +1 -0
  36. package/dist/handlers/prompts/index.js.map +1 -1
  37. package/dist/handlers/resources/index.d.ts.map +1 -1
  38. package/dist/handlers/resources/index.js +257 -13
  39. package/dist/handlers/resources/index.js.map +1 -1
  40. package/dist/handlers/tools/index.d.ts.map +1 -1
  41. package/dist/handlers/tools/index.js +590 -7
  42. package/dist/handlers/tools/index.js.map +1 -1
  43. package/dist/server/McpServer.d.ts +2 -0
  44. package/dist/server/McpServer.d.ts.map +1 -1
  45. package/dist/server/McpServer.js +69 -26
  46. package/dist/server/McpServer.js.map +1 -1
  47. package/dist/types/index.d.ts +97 -0
  48. package/dist/types/index.d.ts.map +1 -1
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/utils/logger.d.ts +1 -0
  51. package/dist/utils/logger.d.ts.map +1 -1
  52. package/dist/utils/logger.js +8 -1
  53. package/dist/utils/logger.js.map +1 -1
  54. package/dist/utils/security-utils.d.ts +8 -0
  55. package/dist/utils/security-utils.d.ts.map +1 -1
  56. package/dist/utils/security-utils.js +32 -0
  57. package/dist/utils/security-utils.js.map +1 -1
  58. package/dist/vector/VectorSearchManager.d.ts +2 -1
  59. package/dist/vector/VectorSearchManager.d.ts.map +1 -1
  60. package/dist/vector/VectorSearchManager.js +100 -34
  61. package/dist/vector/VectorSearchManager.js.map +1 -1
  62. package/docker-compose.yml +46 -37
  63. package/mcp-config-example.json +0 -2
  64. package/package.json +12 -11
  65. package/releases/v4.3.1.md +69 -0
  66. package/releases/v4.4.0.md +120 -0
  67. package/server.json +3 -3
  68. package/src/cli.ts +11 -0
  69. package/src/constants/ServerInstructions.ts +70 -26
  70. package/src/constants/icons.ts +7 -0
  71. package/src/database/SqliteAdapter.ts +162 -32
  72. package/src/filtering/ToolFilter.ts +7 -1
  73. package/src/github/GitHubIntegration.ts +588 -8
  74. package/src/handlers/prompts/index.ts +1 -0
  75. package/src/handlers/resources/index.ts +318 -12
  76. package/src/handlers/tools/index.ts +681 -12
  77. package/src/server/McpServer.ts +79 -37
  78. package/src/types/index.ts +98 -0
  79. package/src/utils/logger.ts +10 -1
  80. package/src/utils/security-utils.ts +35 -0
  81. package/src/vector/VectorSearchManager.ts +110 -39
  82. package/tests/constants/icons.test.ts +102 -0
  83. package/tests/constants/server-instructions.test.ts +549 -0
  84. package/tests/database/sqlite-adapter.bench.ts +63 -0
  85. package/tests/database/sqlite-adapter.test.ts +555 -0
  86. package/tests/filtering/tool-filter.test.ts +266 -0
  87. package/tests/github/github-integration.test.ts +1024 -0
  88. package/tests/handlers/github-resource-handlers.test.ts +473 -0
  89. package/tests/handlers/github-tool-handlers.test.ts +556 -0
  90. package/tests/handlers/prompt-handlers.test.ts +91 -0
  91. package/tests/handlers/resource-handlers.test.ts +339 -0
  92. package/tests/handlers/tool-handlers.test.ts +497 -0
  93. package/tests/handlers/vector-tool-handlers.test.ts +238 -0
  94. package/tests/server/mcp-server.bench.ts +55 -0
  95. package/tests/server/mcp-server.test.ts +675 -0
  96. package/tests/utils/logger.test.ts +180 -0
  97. package/tests/utils/mcp-logger.test.ts +212 -0
  98. package/tests/utils/progress-utils.test.ts +156 -0
  99. package/tests/utils/security-utils.test.ts +82 -0
  100. package/tests/vector/vector-search-manager.test.ts +335 -0
  101. package/tests/vector/vector-search.bench.ts +53 -0
  102. package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
  103. 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
+ })