memory-journal-mcp 4.4.2 → 4.5.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/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/security-update.yml +1 -1
- package/CHANGELOG.md +81 -1
- package/DOCKER_README.md +57 -7
- package/Dockerfile +17 -17
- package/README.md +65 -6
- package/SECURITY.md +27 -35
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +137 -83
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +2 -1
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +15 -8
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/handlers/resources/index.d.ts +3 -1
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +5 -2
- 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 +63 -16
- 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 +43 -2
- package/dist/server/McpServer.js.map +1 -1
- package/dist/server/Scheduler.d.ts +91 -0
- package/dist/server/Scheduler.d.ts.map +1 -0
- package/dist/server/Scheduler.js +201 -0
- package/dist/server/Scheduler.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +0 -21
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +0 -47
- package/dist/utils/security-utils.js.map +1 -1
- package/hooks/README.md +107 -0
- package/hooks/cursor/hooks.json +10 -0
- package/hooks/cursor/memory-journal.mdc +22 -0
- package/hooks/cursor/session-end.sh +19 -0
- package/hooks/kilo-code/session-end-mode.json +11 -0
- package/hooks/kiro/session-end.md +13 -0
- package/package.json +8 -8
- package/releases/v4.5.0.md +116 -0
- package/scripts/generate-server-instructions.ts +176 -0
- package/scripts/server-instructions-function-body.ts +77 -0
- package/server.json +3 -3
- package/src/cli.ts +26 -0
- package/src/constants/ServerInstructions.ts +137 -83
- package/src/constants/server-instructions.md +262 -0
- package/src/database/SqliteAdapter.ts +22 -8
- package/src/handlers/resources/index.ts +8 -2
- package/src/handlers/tools/index.ts +70 -20
- package/src/server/McpServer.ts +60 -2
- package/src/server/Scheduler.ts +278 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/tests/constants/server-instructions.test.ts +26 -0
- package/tests/database/sqlite-adapter.test.ts +84 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/github-resource-handlers.test.ts +453 -0
- package/tests/handlers/github-tool-handlers.test.ts +899 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handlers.test.ts +32 -0
- package/tests/handlers/tool-handlers.test.ts +13 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +491 -5
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/.vscode/settings.json +0 -84
|
@@ -87,5 +87,45 @@ describe('Prompt Handlers', () => {
|
|
|
87
87
|
it('should throw for unknown prompt name', () => {
|
|
88
88
|
expect(() => getPrompt('nonexistent_prompt_xyz', {}, db)).toThrow()
|
|
89
89
|
})
|
|
90
|
+
|
|
91
|
+
it('should return messages for analyze-period with date arguments', () => {
|
|
92
|
+
const today = new Date().toISOString().split('T')[0]!
|
|
93
|
+
const result = getPrompt('analyze-period', { start_date: today, end_date: today }, db)
|
|
94
|
+
|
|
95
|
+
expect(result.messages).toBeDefined()
|
|
96
|
+
expect(result.messages.length).toBeGreaterThan(0)
|
|
97
|
+
const text = (result.messages[0]?.content as { text: string }).text
|
|
98
|
+
expect(text).toContain(today)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should return messages for find-related with query argument', () => {
|
|
102
|
+
const result = getPrompt('find-related', { query: 'test' }, db)
|
|
103
|
+
|
|
104
|
+
expect(result.messages).toBeDefined()
|
|
105
|
+
expect(result.messages.length).toBeGreaterThan(0)
|
|
106
|
+
const text = (result.messages[0]?.content as { text: string }).text
|
|
107
|
+
expect(text).toContain('test')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should return messages for get-context-bundle prompt', () => {
|
|
111
|
+
const result = getPrompt('get-context-bundle', {}, db)
|
|
112
|
+
|
|
113
|
+
expect(result.messages).toBeDefined()
|
|
114
|
+
expect(result.messages.length).toBeGreaterThan(0)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should return messages for goal-tracker prompt', () => {
|
|
118
|
+
const result = getPrompt('goal-tracker', {}, db)
|
|
119
|
+
|
|
120
|
+
expect(result.messages).toBeDefined()
|
|
121
|
+
expect(result.messages.length).toBeGreaterThan(0)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('should return messages for prepare-retro with days argument', () => {
|
|
125
|
+
const result = getPrompt('prepare-retro', { days: '14' }, db)
|
|
126
|
+
|
|
127
|
+
expect(result.messages).toBeDefined()
|
|
128
|
+
expect(result.messages.length).toBeGreaterThan(0)
|
|
129
|
+
})
|
|
90
130
|
})
|
|
91
131
|
})
|
|
@@ -335,5 +335,37 @@ describe('Resource Handlers', () => {
|
|
|
335
335
|
const data = result.data as { prNumber: number }
|
|
336
336
|
expect(data.prNumber).toBe(15)
|
|
337
337
|
})
|
|
338
|
+
|
|
339
|
+
it('should handle resource URI with query parameters', async () => {
|
|
340
|
+
// Query params should be stripped for matching but the full URI is passed to the handler
|
|
341
|
+
const result = await readResource('memory://recent?limit=5', db)
|
|
342
|
+
const data = result.data as { entries: unknown[]; count: number }
|
|
343
|
+
expect(data.entries).toBeDefined()
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('should handle resource URI with hash fragment', async () => {
|
|
347
|
+
const result = await readResource('memory://recent#section', db)
|
|
348
|
+
const data = result.data as { entries: unknown[]; count: number }
|
|
349
|
+
expect(data.entries).toBeDefined()
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('should handle template URI with query parameters', async () => {
|
|
353
|
+
const result = await readResource('memory://projects/42/timeline?sort=asc', db)
|
|
354
|
+
const data = result.data as { projectNumber: number }
|
|
355
|
+
expect(data.projectNumber).toBe(42)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('should return no-github diagram for kanban without integration', async () => {
|
|
359
|
+
const result = await readResource(
|
|
360
|
+
'memory://kanban/1/diagram',
|
|
361
|
+
db,
|
|
362
|
+
undefined,
|
|
363
|
+
undefined,
|
|
364
|
+
null // no github
|
|
365
|
+
)
|
|
366
|
+
const data = result.data as { format: string; diagram: string; message: string }
|
|
367
|
+
expect(data.format).toBe('mermaid')
|
|
368
|
+
expect(data.diagram).toContain('GitHub integration not available')
|
|
369
|
+
})
|
|
338
370
|
})
|
|
339
371
|
})
|
|
@@ -96,7 +96,7 @@ describe('Tool Handlers', () => {
|
|
|
96
96
|
'create_entry',
|
|
97
97
|
{
|
|
98
98
|
content: 'Full tool entry',
|
|
99
|
-
entry_type: '
|
|
99
|
+
entry_type: 'project_decision',
|
|
100
100
|
tags: ['tool-tag-a', 'tool-tag-b'],
|
|
101
101
|
is_personal: false,
|
|
102
102
|
significance_type: 'milestone',
|
|
@@ -105,9 +105,20 @@ describe('Tool Handlers', () => {
|
|
|
105
105
|
)) as { success: boolean; entry: { entryType: string; tags: string[] } }
|
|
106
106
|
|
|
107
107
|
expect(result.success).toBe(true)
|
|
108
|
-
expect(result.entry.entryType).toBe('
|
|
108
|
+
expect(result.entry.entryType).toBe('project_decision')
|
|
109
109
|
expect(result.entry.tags).toContain('tool-tag-a')
|
|
110
110
|
})
|
|
111
|
+
|
|
112
|
+
it('should set isPersonal to false when share_with_team is true', async () => {
|
|
113
|
+
const result = (await callTool(
|
|
114
|
+
'create_entry',
|
|
115
|
+
{ content: 'Team shared entry', share_with_team: true },
|
|
116
|
+
db
|
|
117
|
+
)) as { success: boolean; entry: { isPersonal: boolean } }
|
|
118
|
+
|
|
119
|
+
expect(result.success).toBe(true)
|
|
120
|
+
expect(result.entry.isPersonal).toBe(false)
|
|
121
|
+
})
|
|
111
122
|
})
|
|
112
123
|
|
|
113
124
|
describe('callTool - create_entry_minimal', () => {
|
|
@@ -10,11 +10,8 @@ import { SqliteAdapter } from '../../src/database/SqliteAdapter.js'
|
|
|
10
10
|
import {
|
|
11
11
|
validateDateFormatPattern,
|
|
12
12
|
sanitizeSearchQuery,
|
|
13
|
-
containsSqlInjection,
|
|
14
|
-
assertNoSqlInjection,
|
|
15
13
|
assertNoPathTraversal,
|
|
16
14
|
InvalidDateFormatError,
|
|
17
|
-
SqlInjectionError,
|
|
18
15
|
PathTraversalError,
|
|
19
16
|
} from '../../src/utils/security-utils.js'
|
|
20
17
|
|
|
@@ -120,52 +117,6 @@ describe('Security Utilities', () => {
|
|
|
120
117
|
})
|
|
121
118
|
})
|
|
122
119
|
|
|
123
|
-
describe('containsSqlInjection', () => {
|
|
124
|
-
it('should detect stacked query injection', () => {
|
|
125
|
-
expect(containsSqlInjection('; DROP TABLE users')).toBe(true)
|
|
126
|
-
expect(containsSqlInjection("'; DELETE FROM logs --")).toBe(true)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('should detect comment injection', () => {
|
|
130
|
-
expect(containsSqlInjection('value -- comment')).toBe(true)
|
|
131
|
-
expect(containsSqlInjection('test /* block */ comment')).toBe(true)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('should detect UNION injection', () => {
|
|
135
|
-
expect(containsSqlInjection("' UNION SELECT * FROM users")).toBe(true)
|
|
136
|
-
expect(containsSqlInjection("' UNION ALL SELECT 1")).toBe(true)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('should detect boolean bypass', () => {
|
|
140
|
-
expect(containsSqlInjection("' OR '1'='1")).toBe(true)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('should detect SQLite-specific attacks', () => {
|
|
144
|
-
expect(containsSqlInjection("ATTACH DATABASE 'mal.db' AS x")).toBe(true)
|
|
145
|
-
expect(containsSqlInjection("load_extension('evil.so')")).toBe(true)
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('should not flag safe inputs', () => {
|
|
149
|
-
for (const input of SAFE_INPUTS) {
|
|
150
|
-
expect(containsSqlInjection(input)).toBe(false)
|
|
151
|
-
}
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
describe('assertNoSqlInjection', () => {
|
|
156
|
-
it('should throw SqlInjectionError for injection attempts', () => {
|
|
157
|
-
for (const payload of INJECTION_PAYLOADS) {
|
|
158
|
-
expect(() => assertNoSqlInjection(payload)).toThrow(SqlInjectionError)
|
|
159
|
-
}
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('should not throw for safe inputs', () => {
|
|
163
|
-
for (const input of SAFE_INPUTS) {
|
|
164
|
-
expect(() => assertNoSqlInjection(input)).not.toThrow()
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
|
|
169
120
|
describe('assertNoPathTraversal', () => {
|
|
170
121
|
it('should throw for path traversal attempts', () => {
|
|
171
122
|
expect(() => assertNoPathTraversal('../secret')).toThrow(PathTraversalError)
|
|
@@ -314,16 +265,14 @@ describe('SqliteAdapter SQL Injection Protection', () => {
|
|
|
314
265
|
describe('restoreFromFile - Path Traversal Protection', () => {
|
|
315
266
|
it('should reject filenames with path traversal', async () => {
|
|
316
267
|
await expect(db.restoreFromFile('../../../etc/passwd')).rejects.toThrow(
|
|
317
|
-
|
|
268
|
+
PathTraversalError
|
|
318
269
|
)
|
|
319
270
|
|
|
320
271
|
await expect(db.restoreFromFile('..\\..\\windows\\system32')).rejects.toThrow(
|
|
321
|
-
|
|
272
|
+
PathTraversalError
|
|
322
273
|
)
|
|
323
274
|
|
|
324
|
-
await expect(db.restoreFromFile('/etc/passwd')).rejects.toThrow(
|
|
325
|
-
'Invalid backup filename: path separators not allowed'
|
|
326
|
-
)
|
|
275
|
+
await expect(db.restoreFromFile('/etc/passwd')).rejects.toThrow(PathTraversalError)
|
|
327
276
|
})
|
|
328
277
|
})
|
|
329
278
|
|