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.
Files changed (109) 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 +14 -12
  11. package/README.md +68 -33
  12. package/SECURITY.md +225 -220
  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 +143 -43
  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 +595 -8
  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/progress-utils.d.ts +18 -3
  55. package/dist/utils/progress-utils.d.ts.map +1 -1
  56. package/dist/utils/progress-utils.js.map +1 -1
  57. package/dist/utils/security-utils.d.ts +91 -0
  58. package/dist/utils/security-utils.d.ts.map +1 -0
  59. package/dist/utils/security-utils.js +184 -0
  60. package/dist/utils/security-utils.js.map +1 -0
  61. package/dist/vector/VectorSearchManager.d.ts +2 -1
  62. package/dist/vector/VectorSearchManager.d.ts.map +1 -1
  63. package/dist/vector/VectorSearchManager.js +100 -34
  64. package/dist/vector/VectorSearchManager.js.map +1 -1
  65. package/docker-compose.yml +46 -37
  66. package/mcp-config-example.json +0 -2
  67. package/package.json +21 -14
  68. package/releases/v4.3.1.md +69 -0
  69. package/releases/v4.4.0.md +120 -0
  70. package/server.json +3 -3
  71. package/src/cli.ts +11 -0
  72. package/src/constants/ServerInstructions.ts +70 -26
  73. package/src/constants/icons.ts +7 -0
  74. package/src/database/SqliteAdapter.ts +165 -44
  75. package/src/filtering/ToolFilter.ts +7 -1
  76. package/src/github/GitHubIntegration.ts +588 -8
  77. package/src/handlers/prompts/index.ts +1 -0
  78. package/src/handlers/resources/index.ts +318 -12
  79. package/src/handlers/tools/index.ts +686 -13
  80. package/src/server/McpServer.ts +79 -37
  81. package/src/types/index.ts +98 -0
  82. package/src/utils/logger.ts +10 -1
  83. package/src/utils/progress-utils.ts +17 -6
  84. package/src/utils/security-utils.ts +205 -0
  85. package/src/vector/VectorSearchManager.ts +110 -39
  86. package/tests/constants/icons.test.ts +102 -0
  87. package/tests/constants/server-instructions.test.ts +549 -0
  88. package/tests/database/sqlite-adapter.bench.ts +63 -0
  89. package/tests/database/sqlite-adapter.test.ts +555 -0
  90. package/tests/filtering/tool-filter.test.ts +266 -0
  91. package/tests/github/github-integration.test.ts +1024 -0
  92. package/tests/handlers/github-resource-handlers.test.ts +473 -0
  93. package/tests/handlers/github-tool-handlers.test.ts +556 -0
  94. package/tests/handlers/prompt-handlers.test.ts +91 -0
  95. package/tests/handlers/resource-handlers.test.ts +339 -0
  96. package/tests/handlers/tool-handlers.test.ts +497 -0
  97. package/tests/handlers/vector-tool-handlers.test.ts +238 -0
  98. package/tests/security/sql-injection.test.ts +347 -0
  99. package/tests/server/mcp-server.bench.ts +55 -0
  100. package/tests/server/mcp-server.test.ts +675 -0
  101. package/tests/utils/logger.test.ts +180 -0
  102. package/tests/utils/mcp-logger.test.ts +212 -0
  103. package/tests/utils/progress-utils.test.ts +156 -0
  104. package/tests/utils/security-utils.test.ts +82 -0
  105. package/tests/vector/vector-search-manager.test.ts +335 -0
  106. package/tests/vector/vector-search.bench.ts +53 -0
  107. package/vitest.config.ts +15 -0
  108. package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
  109. package/.github/workflows/dependabot-auto-merge.yml +0 -42
@@ -0,0 +1,266 @@
1
+ /**
2
+ * ToolFilter Tests
3
+ *
4
+ * Tests the tool filtering system: groups, meta-groups, parsing, filtering.
5
+ */
6
+
7
+ import { describe, it, expect, vi, afterEach } from 'vitest'
8
+ import {
9
+ TOOL_GROUPS,
10
+ META_GROUPS,
11
+ getAllToolNames,
12
+ getToolGroup,
13
+ parseToolFilter,
14
+ isToolEnabled,
15
+ filterTools,
16
+ getToolFilterFromEnv,
17
+ calculateTokenSavings,
18
+ getFilterSummary,
19
+ } from '../../src/filtering/ToolFilter.js'
20
+
21
+ // ============================================================================
22
+ // Constants
23
+ // ============================================================================
24
+
25
+ describe('TOOL_GROUPS', () => {
26
+ it('should contain all 8 groups', () => {
27
+ const groups = Object.keys(TOOL_GROUPS)
28
+ expect(groups).toContain('core')
29
+ expect(groups).toContain('search')
30
+ expect(groups).toContain('analytics')
31
+ expect(groups).toContain('relationships')
32
+ expect(groups).toContain('export')
33
+ expect(groups).toContain('admin')
34
+ expect(groups).toContain('github')
35
+ expect(groups).toContain('backup')
36
+ })
37
+
38
+ it('should have no duplicate tools across groups', () => {
39
+ const allTools = Object.values(TOOL_GROUPS).flat()
40
+ const unique = new Set(allTools)
41
+ expect(unique.size).toBe(allTools.length)
42
+ })
43
+ })
44
+
45
+ describe('META_GROUPS', () => {
46
+ it('should define starter as core + search', () => {
47
+ expect(META_GROUPS.starter).toEqual(['core', 'search'])
48
+ })
49
+
50
+ it('should define essential as core only', () => {
51
+ expect(META_GROUPS.essential).toEqual(['core'])
52
+ })
53
+
54
+ it('should define full with all groups', () => {
55
+ expect(META_GROUPS.full).toContain('core')
56
+ expect(META_GROUPS.full).toContain('github')
57
+ expect(META_GROUPS.full).toContain('backup')
58
+ })
59
+
60
+ it('should define readonly without admin, github, backup', () => {
61
+ expect(META_GROUPS.readonly).not.toContain('admin')
62
+ expect(META_GROUPS.readonly).not.toContain('github')
63
+ expect(META_GROUPS.readonly).not.toContain('backup')
64
+ })
65
+ })
66
+
67
+ // ============================================================================
68
+ // getAllToolNames
69
+ // ============================================================================
70
+
71
+ describe('getAllToolNames', () => {
72
+ it('should return all tools', () => {
73
+ const names = getAllToolNames()
74
+ expect(names.length).toBeGreaterThan(0)
75
+ })
76
+
77
+ it('should contain no duplicates', () => {
78
+ const names = getAllToolNames()
79
+ const unique = new Set(names)
80
+ expect(unique.size).toBe(names.length)
81
+ })
82
+
83
+ it('should include known tools', () => {
84
+ const names = getAllToolNames()
85
+ expect(names).toContain('create_entry')
86
+ expect(names).toContain('search_entries')
87
+ expect(names).toContain('backup_journal')
88
+ })
89
+ })
90
+
91
+ // ============================================================================
92
+ // getToolGroup
93
+ // ============================================================================
94
+
95
+ describe('getToolGroup', () => {
96
+ it('should return correct group for known tools', () => {
97
+ expect(getToolGroup('create_entry')).toBe('core')
98
+ expect(getToolGroup('search_entries')).toBe('search')
99
+ expect(getToolGroup('backup_journal')).toBe('backup')
100
+ })
101
+
102
+ it('should return undefined for unknown tools', () => {
103
+ expect(getToolGroup('nonexistent_tool')).toBeUndefined()
104
+ })
105
+ })
106
+
107
+ // ============================================================================
108
+ // Type guards
109
+ // ============================================================================
110
+
111
+ describe('group validation via getToolGroup', () => {
112
+ it('should identify valid groups from tool lookups', () => {
113
+ expect(getToolGroup('create_entry')).toBe('core')
114
+ expect(getToolGroup('search_entries')).toBe('search')
115
+ expect(getToolGroup('delete_entry')).toBe('admin')
116
+ })
117
+
118
+ it('should return undefined for non-tool strings', () => {
119
+ expect(getToolGroup('starter')).toBeUndefined() // meta-group, not a tool
120
+ expect(getToolGroup('nonexistent')).toBeUndefined()
121
+ })
122
+ })
123
+
124
+ // ============================================================================
125
+ // parseToolFilter
126
+ // ============================================================================
127
+
128
+ describe('parseToolFilter', () => {
129
+ it('should parse a single group', () => {
130
+ const config = parseToolFilter('core')
131
+ expect(config.enabledTools.has('create_entry')).toBe(true)
132
+ expect(config.enabledTools.has('backup_journal')).toBe(false)
133
+ })
134
+
135
+ it('should parse multiple groups', () => {
136
+ const config = parseToolFilter('core,search')
137
+ expect(config.enabledTools.has('create_entry')).toBe(true)
138
+ expect(config.enabledTools.has('search_entries')).toBe(true)
139
+ expect(config.enabledTools.has('backup_journal')).toBe(false)
140
+ })
141
+
142
+ it('should parse meta-groups', () => {
143
+ const config = parseToolFilter('starter')
144
+ // starter = core + search
145
+ expect(config.enabledTools.has('create_entry')).toBe(true)
146
+ expect(config.enabledTools.has('search_entries')).toBe(true)
147
+ })
148
+
149
+ it('should parse group exclusion', () => {
150
+ const config = parseToolFilter('full,-admin')
151
+ expect(config.enabledTools.has('create_entry')).toBe(true)
152
+ // Admin tools should be excluded
153
+ expect(config.enabledTools.has('delete_entry')).toBe(false)
154
+ expect(config.enabledTools.has('merge_tags')).toBe(false)
155
+ })
156
+
157
+ it('should parse tool exclusion', () => {
158
+ const config = parseToolFilter('core,-delete_entry')
159
+ expect(config.enabledTools.has('create_entry')).toBe(true)
160
+ expect(config.enabledTools.has('delete_entry')).toBe(false)
161
+ })
162
+
163
+ it('should parse tool addition with +', () => {
164
+ const config = parseToolFilter('core,+semantic_search')
165
+ expect(config.enabledTools.has('create_entry')).toBe(true)
166
+ expect(config.enabledTools.has('semantic_search')).toBe(true)
167
+ })
168
+
169
+ it('should store raw filter string', () => {
170
+ const config = parseToolFilter('starter,-delete_entry')
171
+ expect(config.raw).toBe('starter,-delete_entry')
172
+ })
173
+ })
174
+
175
+ // ============================================================================
176
+ // isToolEnabled / filterTools
177
+ // ============================================================================
178
+
179
+ describe('isToolEnabled', () => {
180
+ it('should return true for enabled tools', () => {
181
+ const config = parseToolFilter('core')
182
+ expect(isToolEnabled('create_entry', config)).toBe(true)
183
+ })
184
+
185
+ it('should return false for disabled tools', () => {
186
+ const config = parseToolFilter('core')
187
+ expect(isToolEnabled('backup_journal', config)).toBe(false)
188
+ })
189
+ })
190
+
191
+ describe('filterTools', () => {
192
+ it('should filter tool objects by name', () => {
193
+ const tools = [
194
+ { name: 'create_entry', other: 'data' },
195
+ { name: 'backup_journal', other: 'data' },
196
+ { name: 'search_entries', other: 'data' },
197
+ ]
198
+ const config = parseToolFilter('core')
199
+ const filtered = filterTools(tools, config)
200
+
201
+ expect(filtered.some((t) => t.name === 'create_entry')).toBe(true)
202
+ expect(filtered.some((t) => t.name === 'backup_journal')).toBe(false)
203
+ })
204
+ })
205
+
206
+ // ============================================================================
207
+ // getToolFilterFromEnv
208
+ // ============================================================================
209
+
210
+ describe('getToolFilterFromEnv', () => {
211
+ afterEach(() => {
212
+ delete process.env['MEMORY_JOURNAL_MCP_TOOL_FILTER']
213
+ })
214
+
215
+ it('should return null when env var not set', () => {
216
+ delete process.env['MEMORY_JOURNAL_MCP_TOOL_FILTER']
217
+ expect(getToolFilterFromEnv()).toBeNull()
218
+ })
219
+
220
+ it('should parse env var when set', () => {
221
+ process.env['MEMORY_JOURNAL_MCP_TOOL_FILTER'] = 'starter'
222
+ const config = getToolFilterFromEnv()
223
+
224
+ expect(config).not.toBeNull()
225
+ expect(config?.enabledTools.has('create_entry')).toBe(true)
226
+ })
227
+ })
228
+
229
+ // ============================================================================
230
+ // calculateTokenSavings
231
+ // ============================================================================
232
+
233
+ describe('calculateTokenSavings', () => {
234
+ it('should calculate correct reduction percentage', () => {
235
+ const result = calculateTokenSavings(38, 10)
236
+ // (38-10)/38 ≈ 73.68%
237
+ expect(result.reduction).toBeGreaterThan(70)
238
+ expect(result.reduction).toBeLessThan(80)
239
+ })
240
+
241
+ it('should calculate saved tokens', () => {
242
+ const result = calculateTokenSavings(38, 10, 150)
243
+ // (38-10) * 150 = 4200
244
+ expect(result.savedTokens).toBe(4200)
245
+ })
246
+
247
+ it('should return 0 when no tools filtered', () => {
248
+ const result = calculateTokenSavings(38, 38)
249
+ expect(result.reduction).toBe(0)
250
+ expect(result.savedTokens).toBe(0)
251
+ })
252
+ })
253
+
254
+ // ============================================================================
255
+ // getFilterSummary
256
+ // ============================================================================
257
+
258
+ describe('getFilterSummary', () => {
259
+ it('should return human-readable summary', () => {
260
+ const config = parseToolFilter('starter')
261
+ const summary = getFilterSummary(config)
262
+
263
+ expect(typeof summary).toBe('string')
264
+ expect(summary.length).toBeGreaterThan(0)
265
+ })
266
+ })