memory-journal-mcp 4.4.2 → 5.0.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/codeql.yml +1 -6
- package/.github/workflows/docker-publish.yml +15 -49
- package/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/secrets-scanning.yml +4 -3
- package/.github/workflows/security-update.yml +3 -3
- package/CHANGELOG.md +213 -0
- package/CONTRIBUTING.md +132 -97
- package/DOCKER_README.md +184 -235
- package/Dockerfile +27 -24
- package/README.md +218 -190
- package/SECURITY.md +27 -35
- package/dist/cli.js +16 -1
- 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 +133 -73
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -2
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +7 -6
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +37 -24
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +319 -157
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/database/schema.d.ts +45 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +92 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +13 -2
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +1 -3
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/github.d.ts +12 -0
- package/dist/handlers/prompts/github.d.ts.map +1 -0
- package/dist/handlers/prompts/github.js +178 -0
- package/dist/handlers/prompts/github.js.map +1 -0
- package/dist/handlers/prompts/index.d.ts +23 -2
- package/dist/handlers/prompts/index.d.ts.map +1 -1
- package/dist/handlers/prompts/index.js +7 -432
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/prompts/workflow.d.ts +12 -0
- package/dist/handlers/prompts/workflow.d.ts.map +1 -0
- package/dist/handlers/prompts/workflow.js +277 -0
- package/dist/handlers/prompts/workflow.js.map +1 -0
- package/dist/handlers/resources/core.d.ts +11 -0
- package/dist/handlers/resources/core.d.ts.map +1 -0
- package/dist/handlers/resources/core.js +433 -0
- package/dist/handlers/resources/core.js.map +1 -0
- package/dist/handlers/resources/github.d.ts +11 -0
- package/dist/handlers/resources/github.d.ts.map +1 -0
- package/dist/handlers/resources/github.js +314 -0
- package/dist/handlers/resources/github.js.map +1 -0
- package/dist/handlers/resources/graph.d.ts +11 -0
- package/dist/handlers/resources/graph.d.ts.map +1 -0
- package/dist/handlers/resources/graph.js +204 -0
- package/dist/handlers/resources/graph.js.map +1 -0
- package/dist/handlers/resources/index.d.ts +5 -20
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +16 -1278
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/resources/shared.d.ts +60 -0
- package/dist/handlers/resources/shared.d.ts.map +1 -0
- package/dist/handlers/resources/shared.js +49 -0
- package/dist/handlers/resources/shared.js.map +1 -0
- package/dist/handlers/resources/team.d.ts +13 -0
- package/dist/handlers/resources/team.d.ts.map +1 -0
- package/dist/handlers/resources/team.js +119 -0
- package/dist/handlers/resources/team.js.map +1 -0
- package/dist/handlers/resources/templates.d.ts +13 -0
- package/dist/handlers/resources/templates.d.ts.map +1 -0
- package/dist/handlers/resources/templates.js +310 -0
- package/dist/handlers/resources/templates.js.map +1 -0
- package/dist/handlers/tools/admin.d.ts +8 -0
- package/dist/handlers/tools/admin.d.ts.map +1 -0
- package/dist/handlers/tools/admin.js +270 -0
- package/dist/handlers/tools/admin.js.map +1 -0
- package/dist/handlers/tools/analytics.d.ts +8 -0
- package/dist/handlers/tools/analytics.d.ts.map +1 -0
- package/dist/handlers/tools/analytics.js +256 -0
- package/dist/handlers/tools/analytics.js.map +1 -0
- package/dist/handlers/tools/backup.d.ts +8 -0
- package/dist/handlers/tools/backup.d.ts.map +1 -0
- package/dist/handlers/tools/backup.js +224 -0
- package/dist/handlers/tools/backup.js.map +1 -0
- package/dist/handlers/tools/core.d.ts +9 -0
- package/dist/handlers/tools/core.d.ts.map +1 -0
- package/dist/handlers/tools/core.js +326 -0
- package/dist/handlers/tools/core.js.map +1 -0
- package/dist/handlers/tools/export.d.ts +8 -0
- package/dist/handlers/tools/export.d.ts.map +1 -0
- package/dist/handlers/tools/export.js +89 -0
- package/dist/handlers/tools/export.js.map +1 -0
- package/dist/handlers/tools/github/helpers.d.ts +34 -0
- package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
- package/dist/handlers/tools/github/helpers.js +52 -0
- package/dist/handlers/tools/github/helpers.js.map +1 -0
- package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
- package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/insights-tools.js +104 -0
- package/dist/handlers/tools/github/insights-tools.js.map +1 -0
- package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
- package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/issue-tools.js +359 -0
- package/dist/handlers/tools/github/issue-tools.js.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.js +108 -0
- package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.js +302 -0
- package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.js +15 -0
- package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
- package/dist/handlers/tools/github/read-tools.d.ts +8 -0
- package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/read-tools.js +260 -0
- package/dist/handlers/tools/github/read-tools.js.map +1 -0
- package/dist/handlers/tools/github/schemas.d.ts +467 -0
- package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/github/schemas.js +335 -0
- package/dist/handlers/tools/github/schemas.js.map +1 -0
- package/dist/handlers/tools/github.d.ts +14 -0
- package/dist/handlers/tools/github.d.ts.map +1 -0
- package/dist/handlers/tools/github.js +28 -0
- package/dist/handlers/tools/github.js.map +1 -0
- package/dist/handlers/tools/index.d.ts +15 -20
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +117 -2909
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/handlers/tools/relationships.d.ts +8 -0
- package/dist/handlers/tools/relationships.d.ts.map +1 -0
- package/dist/handlers/tools/relationships.js +308 -0
- package/dist/handlers/tools/relationships.js.map +1 -0
- package/dist/handlers/tools/schemas.d.ts +108 -0
- package/dist/handlers/tools/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/schemas.js +122 -0
- package/dist/handlers/tools/schemas.js.map +1 -0
- package/dist/handlers/tools/search.d.ts +8 -0
- package/dist/handlers/tools/search.d.ts.map +1 -0
- package/dist/handlers/tools/search.js +282 -0
- package/dist/handlers/tools/search.js.map +1 -0
- package/dist/handlers/tools/team.d.ts +11 -0
- package/dist/handlers/tools/team.d.ts.map +1 -0
- package/dist/handlers/tools/team.js +239 -0
- package/dist/handlers/tools/team.js.map +1 -0
- package/dist/server/McpServer.d.ts +4 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +48 -297
- 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/transports/http.d.ts +66 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +519 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/types/entities.d.ts +101 -0
- package/dist/types/entities.d.ts.map +1 -0
- package/dist/types/entities.js +5 -0
- package/dist/types/entities.js.map +1 -0
- package/dist/types/filtering.d.ts +34 -0
- package/dist/types/filtering.d.ts.map +1 -0
- package/dist/types/filtering.js +5 -0
- package/dist/types/filtering.js.map +1 -0
- package/dist/types/github.d.ts +166 -0
- package/dist/types/github.d.ts.map +1 -0
- package/dist/types/github.js +5 -0
- package/dist/types/github.js.map +1 -0
- package/dist/types/index.d.ts +35 -292
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-helpers.d.ts +37 -0
- package/dist/utils/error-helpers.d.ts.map +1 -0
- package/dist/utils/error-helpers.js +47 -0
- package/dist/utils/error-helpers.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/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +9 -32
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +11 -2
- 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/mcp-config-example.json +1 -0
- package/package.json +11 -9
- package/playwright.config.ts +29 -0
- package/releases/v4.5.0.md +116 -0
- package/releases/v5.0.0.md +105 -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 +45 -1
- package/src/constants/ServerInstructions.ts +133 -73
- package/src/constants/icons.ts +8 -7
- package/src/constants/server-instructions.md +268 -0
- package/src/database/SqliteAdapter.ts +358 -192
- package/src/database/schema.ts +125 -0
- package/src/filtering/ToolFilter.ts +13 -2
- package/src/github/GitHubIntegration.ts +1 -3
- package/src/handlers/prompts/github.ts +209 -0
- package/src/handlers/prompts/index.ts +10 -499
- package/src/handlers/prompts/workflow.ts +314 -0
- package/src/handlers/resources/core.ts +528 -0
- package/src/handlers/resources/github.ts +358 -0
- package/src/handlers/resources/graph.ts +254 -0
- package/src/handlers/resources/index.ts +23 -1570
- package/src/handlers/resources/shared.ts +103 -0
- package/src/handlers/resources/team.ts +133 -0
- package/src/handlers/resources/templates.ts +374 -0
- package/src/handlers/tools/admin.ts +285 -0
- package/src/handlers/tools/analytics.ts +301 -0
- package/src/handlers/tools/backup.ts +242 -0
- package/src/handlers/tools/core.ts +350 -0
- package/src/handlers/tools/export.ts +115 -0
- package/src/handlers/tools/github/helpers.ts +86 -0
- package/src/handlers/tools/github/insights-tools.ts +119 -0
- package/src/handlers/tools/github/issue-tools.ts +439 -0
- package/src/handlers/tools/github/kanban-tools.ts +134 -0
- package/src/handlers/tools/github/milestone-tools.ts +392 -0
- package/src/handlers/tools/github/mutation-tools.ts +17 -0
- package/src/handlers/tools/github/read-tools.ts +328 -0
- package/src/handlers/tools/github/schemas.ts +369 -0
- package/src/handlers/tools/github.ts +36 -0
- package/src/handlers/tools/index.ts +144 -3325
- package/src/handlers/tools/relationships.ts +358 -0
- package/src/handlers/tools/schemas.ts +132 -0
- package/src/handlers/tools/search.ts +343 -0
- package/src/handlers/tools/team.ts +273 -0
- package/src/server/McpServer.ts +63 -358
- package/src/server/Scheduler.ts +278 -0
- package/src/transports/http.ts +635 -0
- package/src/types/entities.ts +145 -0
- package/src/types/filtering.ts +54 -0
- package/src/types/github.ts +180 -0
- package/src/types/index.ts +67 -375
- package/src/utils/error-helpers.ts +52 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/src/vector/VectorSearchManager.ts +9 -33
- package/tests/constants/icons.test.ts +1 -2
- package/tests/constants/server-instructions.test.ts +30 -4
- package/tests/database/sqlite-adapter.test.ts +91 -7
- package/tests/e2e/auth.spec.ts +154 -0
- package/tests/e2e/health.spec.ts +63 -0
- package/tests/e2e/protocols.spec.ts +134 -0
- package/tests/e2e/resources.spec.ts +103 -0
- package/tests/e2e/scheduler.spec.ts +79 -0
- package/tests/e2e/security.spec.ts +91 -0
- package/tests/e2e/sessions.spec.ts +95 -0
- package/tests/e2e/stateless.spec.ts +121 -0
- package/tests/e2e/tools.spec.ts +111 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/error-path-coverage.test.ts +324 -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-handler-coverage.test.ts +106 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handler-coverage.test.ts +181 -0
- package/tests/handlers/resource-handlers.test.ts +33 -9
- package/tests/handlers/search-tool-handlers.test.ts +272 -0
- package/tests/handlers/targeted-gap-closure.test.ts +387 -0
- package/tests/handlers/team-resource-handlers.test.ts +156 -0
- package/tests/handlers/team-tool-handlers.test.ts +301 -0
- package/tests/handlers/tool-handler-coverage.test.ts +469 -0
- package/tests/handlers/tool-handlers.test.ts +2 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +503 -8
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/transports/http-transport.test.ts +620 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/vitest.config.ts +4 -1
- package/.memory-journal-team.db +0 -0
- package/.vscode/settings.json +0 -84
|
@@ -99,6 +99,32 @@ describe('generateInstructions', () => {
|
|
|
99
99
|
)
|
|
100
100
|
expect(result).not.toContain('## GitHub Integration')
|
|
101
101
|
})
|
|
102
|
+
|
|
103
|
+
it('should include Session End section', () => {
|
|
104
|
+
const result = generateInstructions(
|
|
105
|
+
TEST_TOOLS,
|
|
106
|
+
TEST_RESOURCES,
|
|
107
|
+
TEST_PROMPTS,
|
|
108
|
+
undefined,
|
|
109
|
+
'essential'
|
|
110
|
+
)
|
|
111
|
+
expect(result).toContain('Session End')
|
|
112
|
+
expect(result).toContain('session-summary')
|
|
113
|
+
expect(result).toContain('retrospective')
|
|
114
|
+
expect(result).toContain('opt-out')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should include Rule & Skill Suggestions section', () => {
|
|
118
|
+
const result = generateInstructions(
|
|
119
|
+
TEST_TOOLS,
|
|
120
|
+
TEST_RESOURCES,
|
|
121
|
+
TEST_PROMPTS,
|
|
122
|
+
undefined,
|
|
123
|
+
'essential'
|
|
124
|
+
)
|
|
125
|
+
expect(result).toContain('Rule & Skill Suggestions')
|
|
126
|
+
expect(result).toContain('always ask the user first')
|
|
127
|
+
})
|
|
102
128
|
})
|
|
103
129
|
|
|
104
130
|
describe('standard level', () => {
|
|
@@ -466,7 +492,7 @@ describe('generateInstructions', () => {
|
|
|
466
492
|
'memory://graph/recent',
|
|
467
493
|
'memory://graph/actions',
|
|
468
494
|
'memory://actions/recent',
|
|
469
|
-
|
|
495
|
+
|
|
470
496
|
'memory://github/status',
|
|
471
497
|
'memory://github/milestones',
|
|
472
498
|
'memory://github/insights',
|
|
@@ -482,9 +508,9 @@ describe('generateInstructions', () => {
|
|
|
482
508
|
})
|
|
483
509
|
|
|
484
510
|
describe('tool count consistency', () => {
|
|
485
|
-
it('should have
|
|
511
|
+
it('should have 42 tools across all groups', () => {
|
|
486
512
|
const allToolNames = getAllToolNames()
|
|
487
|
-
expect(allToolNames.length).toBe(
|
|
513
|
+
expect(allToolNames.length).toBe(42)
|
|
488
514
|
})
|
|
489
515
|
|
|
490
516
|
it('should show correct active tool count for all tools', () => {
|
|
@@ -492,7 +518,7 @@ describe('generateInstructions', () => {
|
|
|
492
518
|
expect(result).toContain(`Active Tools (${String(ALL_TOOLS.size)})`)
|
|
493
519
|
})
|
|
494
520
|
|
|
495
|
-
it('should list all
|
|
521
|
+
it('should list all 9 tool groups in active tools', () => {
|
|
496
522
|
const result = fullInstructions()
|
|
497
523
|
const groups = Object.keys(TOOL_GROUPS)
|
|
498
524
|
for (const group of groups) {
|
|
@@ -64,7 +64,7 @@ describe('SqliteAdapter', () => {
|
|
|
64
64
|
it('should create an entry with all fields', () => {
|
|
65
65
|
const entry = db.createEntry({
|
|
66
66
|
content: 'Full entry',
|
|
67
|
-
entryType: '
|
|
67
|
+
entryType: 'project_decision',
|
|
68
68
|
tags: ['tag-a', 'tag-b'],
|
|
69
69
|
isPersonal: false,
|
|
70
70
|
significanceType: 'milestone',
|
|
@@ -73,7 +73,7 @@ describe('SqliteAdapter', () => {
|
|
|
73
73
|
issueNumber: 7,
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
expect(entry.entryType).toBe('
|
|
76
|
+
expect(entry.entryType).toBe('project_decision')
|
|
77
77
|
expect(entry.isPersonal).toBe(false)
|
|
78
78
|
expect(entry.tags).toContain('tag-a')
|
|
79
79
|
expect(entry.tags).toContain('tag-b')
|
|
@@ -187,13 +187,13 @@ describe('SqliteAdapter', () => {
|
|
|
187
187
|
|
|
188
188
|
it('should filter by isPersonal', () => {
|
|
189
189
|
db.createEntry({ content: 'Personal entry', isPersonal: true })
|
|
190
|
-
db.createEntry({ content: '
|
|
190
|
+
db.createEntry({ content: 'Non-personal entry', isPersonal: false })
|
|
191
191
|
|
|
192
192
|
const personal = db.getRecentEntries(100, true)
|
|
193
|
-
const
|
|
193
|
+
const nonPersonal = db.getRecentEntries(100, false)
|
|
194
194
|
|
|
195
195
|
expect(personal.every((e) => e.isPersonal)).toBe(true)
|
|
196
|
-
expect(
|
|
196
|
+
expect(nonPersonal.every((e) => !e.isPersonal)).toBe(true)
|
|
197
197
|
})
|
|
198
198
|
})
|
|
199
199
|
|
|
@@ -471,6 +471,65 @@ describe('SqliteAdapter', () => {
|
|
|
471
471
|
fs.unlinkSync(backup.path)
|
|
472
472
|
}
|
|
473
473
|
})
|
|
474
|
+
|
|
475
|
+
it('should delete old backups keeping only keepCount', () => {
|
|
476
|
+
const fs = require('node:fs')
|
|
477
|
+
|
|
478
|
+
// Clean up any pre-existing backups from other tests
|
|
479
|
+
const preExisting = db.listBackups()
|
|
480
|
+
for (const backup of preExisting) {
|
|
481
|
+
if (fs.existsSync(backup.path)) fs.unlinkSync(backup.path)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Create 3 backups
|
|
485
|
+
const b1 = db.exportToFile('cleanup-1')
|
|
486
|
+
const b2 = db.exportToFile('cleanup-2')
|
|
487
|
+
const b3 = db.exportToFile('cleanup-3')
|
|
488
|
+
|
|
489
|
+
// Keep only 1 newest
|
|
490
|
+
db.deleteOldBackups(1)
|
|
491
|
+
|
|
492
|
+
const remaining = db.listBackups()
|
|
493
|
+
// Should have exactly 1 backup remaining (newest)
|
|
494
|
+
expect(remaining.length).toBe(1)
|
|
495
|
+
|
|
496
|
+
// Cleanup any remaining
|
|
497
|
+
for (const path of [b1.path, b2.path, b3.path]) {
|
|
498
|
+
if (fs.existsSync(path)) fs.unlinkSync(path)
|
|
499
|
+
}
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
it('should restore from a backup file', async () => {
|
|
503
|
+
const fs = require('node:fs')
|
|
504
|
+
// Create an entry and backup
|
|
505
|
+
db.createEntry({ content: 'Before restore test' })
|
|
506
|
+
const countBefore = db.getActiveEntryCount()
|
|
507
|
+
const backup = db.exportToFile('restore-test')
|
|
508
|
+
|
|
509
|
+
// Create more entries after backup
|
|
510
|
+
db.createEntry({ content: 'After backup 1' })
|
|
511
|
+
db.createEntry({ content: 'After backup 2' })
|
|
512
|
+
const countAfterAdding = db.getActiveEntryCount()
|
|
513
|
+
expect(countAfterAdding).toBeGreaterThan(countBefore)
|
|
514
|
+
|
|
515
|
+
// Restore should revert to backup state
|
|
516
|
+
const result = await db.restoreFromFile(backup.filename)
|
|
517
|
+
expect(result.previousEntryCount).toBe(countAfterAdding)
|
|
518
|
+
expect(result.newEntryCount).toBe(countBefore)
|
|
519
|
+
|
|
520
|
+
// Cleanup
|
|
521
|
+
const backups = db.listBackups()
|
|
522
|
+
for (const b of backups) {
|
|
523
|
+
const path = require('node:path').join('backups', b.filename)
|
|
524
|
+
if (fs.existsSync(path)) fs.unlinkSync(path)
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('should get raw database handle', () => {
|
|
529
|
+
const rawDb = db.getRawDb()
|
|
530
|
+
expect(rawDb).toBeDefined()
|
|
531
|
+
expect(typeof rawDb.exec).toBe('function')
|
|
532
|
+
})
|
|
474
533
|
})
|
|
475
534
|
|
|
476
535
|
// ========================================================================
|
|
@@ -536,10 +595,10 @@ describe('SqliteAdapter', () => {
|
|
|
536
595
|
it('should filter by entry type', () => {
|
|
537
596
|
const today = new Date().toISOString().split('T')[0]!
|
|
538
597
|
const results = db.searchByDateRange(today, today, {
|
|
539
|
-
entryType: '
|
|
598
|
+
entryType: 'project_decision',
|
|
540
599
|
})
|
|
541
600
|
for (const r of results) {
|
|
542
|
-
expect(r.entryType).toBe('
|
|
601
|
+
expect(r.entryType).toBe('project_decision')
|
|
543
602
|
}
|
|
544
603
|
})
|
|
545
604
|
|
|
@@ -552,4 +611,29 @@ describe('SqliteAdapter', () => {
|
|
|
552
611
|
expect(results.length).toBeGreaterThan(0)
|
|
553
612
|
})
|
|
554
613
|
})
|
|
614
|
+
|
|
615
|
+
// ========================================================================
|
|
616
|
+
// Backup edge cases
|
|
617
|
+
// ========================================================================
|
|
618
|
+
|
|
619
|
+
describe('backup edge cases', () => {
|
|
620
|
+
it('should return empty array when backups directory does not exist', () => {
|
|
621
|
+
// Use a fresh adapter with no backups dir created
|
|
622
|
+
const tempDb = new SqliteAdapter('./test-no-backups.db')
|
|
623
|
+
tempDb.initialize()
|
|
624
|
+
const backups = tempDb.listBackups()
|
|
625
|
+
expect(backups).toEqual([])
|
|
626
|
+
tempDb.close()
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
it('should throw when deleteOldBackups keepCount is less than 1', () => {
|
|
630
|
+
expect(() => db.deleteOldBackups(0)).toThrow('keepCount must be at least 1')
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
it('should throw when restoring from non-existent backup file', async () => {
|
|
634
|
+
await expect(db.restoreFromFile('nonexistent-backup.db')).rejects.toThrow(
|
|
635
|
+
'Backup file not found'
|
|
636
|
+
)
|
|
637
|
+
})
|
|
638
|
+
})
|
|
555
639
|
})
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests: Bearer Token Authentication
|
|
3
|
+
*
|
|
4
|
+
* Tests the --auth-token middleware. Uses a test-local server
|
|
5
|
+
* on port 3101 to avoid conflicting with the main webServer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test, expect } from '@playwright/test'
|
|
9
|
+
import { type ChildProcess, spawn } from 'node:child_process'
|
|
10
|
+
import { setTimeout as delay } from 'node:timers/promises'
|
|
11
|
+
|
|
12
|
+
const AUTH_TOKEN = 'test-secret-token-e2e'
|
|
13
|
+
const AUTH_PORT = 3101
|
|
14
|
+
const AUTH_BASE = `http://localhost:${AUTH_PORT}`
|
|
15
|
+
|
|
16
|
+
let serverProcess: ChildProcess | null = null
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Start a second MCP server with --auth-token on a different port.
|
|
20
|
+
* Waits for /health to become reachable before returning.
|
|
21
|
+
*/
|
|
22
|
+
async function startAuthServer(): Promise<void> {
|
|
23
|
+
serverProcess = spawn(
|
|
24
|
+
'node',
|
|
25
|
+
[
|
|
26
|
+
'dist/cli.js',
|
|
27
|
+
'--transport',
|
|
28
|
+
'http',
|
|
29
|
+
'--port',
|
|
30
|
+
String(AUTH_PORT),
|
|
31
|
+
'--db',
|
|
32
|
+
'./test-e2e-auth.db',
|
|
33
|
+
'--auth-token',
|
|
34
|
+
AUTH_TOKEN,
|
|
35
|
+
],
|
|
36
|
+
{
|
|
37
|
+
cwd: process.cwd(),
|
|
38
|
+
stdio: 'pipe',
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// Wait for server to be ready (poll /health)
|
|
43
|
+
const maxAttempts = 30
|
|
44
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetch(`${AUTH_BASE}/health`)
|
|
47
|
+
if (res.ok) return
|
|
48
|
+
} catch {
|
|
49
|
+
// Server not ready yet
|
|
50
|
+
}
|
|
51
|
+
await delay(500)
|
|
52
|
+
}
|
|
53
|
+
throw new Error('Auth server did not start within timeout')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function stopAuthServer(): void {
|
|
57
|
+
if (serverProcess) {
|
|
58
|
+
serverProcess.kill('SIGTERM')
|
|
59
|
+
serverProcess = null
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test.describe('Bearer Token Authentication', () => {
|
|
64
|
+
test.beforeAll(async () => {
|
|
65
|
+
await startAuthServer()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test.afterAll(() => {
|
|
69
|
+
stopAuthServer()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('/health should be accessible without token (exempt)', async () => {
|
|
73
|
+
const response = await fetch(`${AUTH_BASE}/health`)
|
|
74
|
+
expect(response.status).toBe(200)
|
|
75
|
+
|
|
76
|
+
const body = await response.json()
|
|
77
|
+
expect(body).toHaveProperty('status', 'healthy')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('POST /mcp should return 401 without Authorization header', async () => {
|
|
81
|
+
const response = await fetch(`${AUTH_BASE}/mcp`, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
'Content-Type': 'application/json',
|
|
85
|
+
Accept: 'application/json, text/event-stream',
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
jsonrpc: '2.0',
|
|
89
|
+
id: 1,
|
|
90
|
+
method: 'initialize',
|
|
91
|
+
params: {
|
|
92
|
+
protocolVersion: '2025-03-26',
|
|
93
|
+
capabilities: {},
|
|
94
|
+
clientInfo: { name: 'test', version: '1.0' },
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(response.status).toBe(401)
|
|
100
|
+
const body = await response.json()
|
|
101
|
+
expect(body).toHaveProperty('error', 'Unauthorized')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('POST /mcp should return 401 with wrong token', async () => {
|
|
105
|
+
const response = await fetch(`${AUTH_BASE}/mcp`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: {
|
|
108
|
+
'Content-Type': 'application/json',
|
|
109
|
+
Accept: 'application/json, text/event-stream',
|
|
110
|
+
Authorization: 'Bearer wrong-token',
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
jsonrpc: '2.0',
|
|
114
|
+
id: 1,
|
|
115
|
+
method: 'initialize',
|
|
116
|
+
params: {
|
|
117
|
+
protocolVersion: '2025-03-26',
|
|
118
|
+
capabilities: {},
|
|
119
|
+
clientInfo: { name: 'test', version: '1.0' },
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
expect(response.status).toBe(401)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('POST /mcp should succeed with correct Bearer token', async () => {
|
|
128
|
+
const response = await fetch(`${AUTH_BASE}/mcp`, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
Accept: 'application/json, text/event-stream',
|
|
133
|
+
Authorization: `Bearer ${AUTH_TOKEN}`,
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
jsonrpc: '2.0',
|
|
137
|
+
id: 1,
|
|
138
|
+
method: 'initialize',
|
|
139
|
+
params: {
|
|
140
|
+
protocolVersion: '2025-03-26',
|
|
141
|
+
capabilities: {},
|
|
142
|
+
clientInfo: { name: 'test', version: '1.0' },
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
expect(response.status).toBe(200)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('GET / should return 401 without token', async () => {
|
|
151
|
+
const response = await fetch(`${AUTH_BASE}/`)
|
|
152
|
+
expect(response.status).toBe(401)
|
|
153
|
+
})
|
|
154
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests: Health & Root Info
|
|
3
|
+
*
|
|
4
|
+
* Verifies the HTTP server's health check and root info endpoints
|
|
5
|
+
* return correct responses with expected structure.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test, expect } from '@playwright/test'
|
|
9
|
+
|
|
10
|
+
test.describe('Health & Root Info', () => {
|
|
11
|
+
test('should return 200 OK from /health endpoint', async ({ request }) => {
|
|
12
|
+
const response = await request.get('/health')
|
|
13
|
+
expect(response.status()).toBe(200)
|
|
14
|
+
|
|
15
|
+
const body = await response.json()
|
|
16
|
+
expect(body).toHaveProperty('status', 'healthy')
|
|
17
|
+
expect(body).toHaveProperty('timestamp')
|
|
18
|
+
// Timestamp should be a valid ISO string
|
|
19
|
+
expect(new Date(body.timestamp).toISOString()).toBe(body.timestamp)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('should return server metadata on GET /', async ({ request }) => {
|
|
23
|
+
const response = await request.get('/')
|
|
24
|
+
expect(response.status()).toBe(200)
|
|
25
|
+
|
|
26
|
+
const body = await response.json()
|
|
27
|
+
expect(body).toHaveProperty('name', 'memory-journal-mcp')
|
|
28
|
+
expect(body).toHaveProperty('version')
|
|
29
|
+
expect(body).toHaveProperty('description')
|
|
30
|
+
expect(body).toHaveProperty('endpoints')
|
|
31
|
+
expect(body.endpoints).toHaveProperty('POST /mcp')
|
|
32
|
+
expect(body.endpoints).toHaveProperty('GET /sse')
|
|
33
|
+
expect(body.endpoints).toHaveProperty('GET /health')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('should accept MCP initialization request on /mcp', async ({ request }) => {
|
|
37
|
+
const response = await request.post('/mcp', {
|
|
38
|
+
headers: {
|
|
39
|
+
Accept: 'application/json, text/event-stream',
|
|
40
|
+
},
|
|
41
|
+
data: {
|
|
42
|
+
jsonrpc: '2.0',
|
|
43
|
+
id: 1,
|
|
44
|
+
method: 'initialize',
|
|
45
|
+
params: {
|
|
46
|
+
protocolVersion: '2025-03-26',
|
|
47
|
+
capabilities: {},
|
|
48
|
+
clientInfo: {
|
|
49
|
+
name: 'playwright-test',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
expect(response.status()).toBe(200)
|
|
57
|
+
// Session ID should be returned in response headers
|
|
58
|
+
const sessionId = response.headers()['mcp-session-id']
|
|
59
|
+
expect(sessionId).toBeDefined()
|
|
60
|
+
expect(typeof sessionId).toBe('string')
|
|
61
|
+
expect(sessionId!.length).toBeGreaterThan(0)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests: HTTP Transport Protocols
|
|
3
|
+
*
|
|
4
|
+
* Tests both Streamable HTTP (MCP 2025-03-26) and Legacy SSE (MCP 2024-11-05)
|
|
5
|
+
* protocol error handling and routing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test, expect } from '@playwright/test'
|
|
9
|
+
|
|
10
|
+
test.describe('HTTP Transport Protocols', () => {
|
|
11
|
+
test.describe('Streamable HTTP (MCP 2025-03-26)', () => {
|
|
12
|
+
test('should reject non-init request without session ID', async ({ request }) => {
|
|
13
|
+
const response = await request.post('/mcp', {
|
|
14
|
+
headers: {
|
|
15
|
+
Accept: 'application/json, text/event-stream',
|
|
16
|
+
},
|
|
17
|
+
data: {
|
|
18
|
+
jsonrpc: '2.0',
|
|
19
|
+
id: 1,
|
|
20
|
+
method: 'ping',
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
expect(response.status()).toBe(400)
|
|
25
|
+
const body = await response.json()
|
|
26
|
+
expect(body.error).toHaveProperty(
|
|
27
|
+
'message',
|
|
28
|
+
'Bad Request: No valid session ID provided'
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('should reject malformed JSON body on /mcp', async ({ request }) => {
|
|
33
|
+
const response = await request.post('/mcp', {
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: 'application/json, text/event-stream',
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
},
|
|
38
|
+
data: Buffer.from('{"broken": json}'),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Express JSON parser rejects invalid JSON before reaching our handler
|
|
42
|
+
expect(response.status()).toBe(400)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('should accept initialization and return session ID', async ({ request }) => {
|
|
46
|
+
const response = await request.post('/mcp', {
|
|
47
|
+
headers: {
|
|
48
|
+
Accept: 'application/json, text/event-stream',
|
|
49
|
+
},
|
|
50
|
+
data: {
|
|
51
|
+
jsonrpc: '2.0',
|
|
52
|
+
id: 1,
|
|
53
|
+
method: 'initialize',
|
|
54
|
+
params: {
|
|
55
|
+
protocolVersion: '2025-03-26',
|
|
56
|
+
capabilities: {},
|
|
57
|
+
clientInfo: {
|
|
58
|
+
name: 'playwright-protocol-test',
|
|
59
|
+
version: '1.0.0',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(response.status()).toBe(200)
|
|
66
|
+
const sessionId = response.headers()['mcp-session-id']
|
|
67
|
+
expect(sessionId).toBeDefined()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test.describe('Legacy SSE (MCP 2024-11-05)', () => {
|
|
72
|
+
test('should reject /messages POST without sessionId parameter', async ({ request }) => {
|
|
73
|
+
const response = await request.post('/messages', {
|
|
74
|
+
data: {
|
|
75
|
+
jsonrpc: '2.0',
|
|
76
|
+
id: 1,
|
|
77
|
+
method: 'initialize',
|
|
78
|
+
params: {
|
|
79
|
+
protocolVersion: '2024-11-05',
|
|
80
|
+
capabilities: {},
|
|
81
|
+
clientInfo: { name: 'test', version: '1.0' },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
expect(response.status()).toBe(400)
|
|
87
|
+
const body = await response.json()
|
|
88
|
+
expect(body.error).toHaveProperty('message', 'Missing sessionId parameter')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('should reject /messages POST with unknown sessionId', async ({ request }) => {
|
|
92
|
+
const response = await request.post('/messages?sessionId=invalid-session-uuid', {
|
|
93
|
+
data: {
|
|
94
|
+
jsonrpc: '2.0',
|
|
95
|
+
id: 1,
|
|
96
|
+
method: 'ping',
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
expect(response.status()).toBe(404)
|
|
101
|
+
const body = await response.json()
|
|
102
|
+
expect(body.error).toHaveProperty('message', 'Session not found')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('should complete full SDK client round-trip via Legacy SSE', async () => {
|
|
106
|
+
// Regression test: server.connect() auto-calls start() on SSEServerTransport,
|
|
107
|
+
// so a redundant start() call would throw "already started!" and break SSE entirely.
|
|
108
|
+
const { Client } = await import('@modelcontextprotocol/sdk/client/index.js')
|
|
109
|
+
const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')
|
|
110
|
+
|
|
111
|
+
const transport = new SSEClientTransport(new URL('http://localhost:3100/sse'))
|
|
112
|
+
const client = new Client(
|
|
113
|
+
{ name: 'playwright-sse-regression', version: '1.0.0' },
|
|
114
|
+
{ capabilities: {} }
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await client.connect(transport)
|
|
119
|
+
|
|
120
|
+
const response = await client.callTool({
|
|
121
|
+
name: 'test_simple',
|
|
122
|
+
arguments: { message: 'SSE round-trip' },
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
expect(response.isError).toBeUndefined()
|
|
126
|
+
expect(Array.isArray(response.content)).toBe(true)
|
|
127
|
+
const text = (response.content[0] as { type: string; text: string }).text
|
|
128
|
+
expect(text).toContain('SSE round-trip')
|
|
129
|
+
} finally {
|
|
130
|
+
await client.close()
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests: MCP Resource Reads via SDK Client
|
|
3
|
+
*
|
|
4
|
+
* Uses the official @modelcontextprotocol/sdk client to connect
|
|
5
|
+
* via Streamable HTTP transport and read resources end-to-end.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test, expect } from '@playwright/test'
|
|
9
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
10
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
11
|
+
|
|
12
|
+
test.describe.configure({ mode: 'serial' })
|
|
13
|
+
|
|
14
|
+
test.describe('E2E Resource Reads (via MCP SDK Client)', () => {
|
|
15
|
+
let client: Client
|
|
16
|
+
|
|
17
|
+
test.beforeAll(async () => {
|
|
18
|
+
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3100/mcp'))
|
|
19
|
+
client = new Client(
|
|
20
|
+
{ name: 'playwright-resource-test', version: '1.0.0' },
|
|
21
|
+
{ capabilities: {} }
|
|
22
|
+
)
|
|
23
|
+
await client.connect(transport)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test.afterAll(async () => {
|
|
27
|
+
await client.close()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('should list available resources', async () => {
|
|
31
|
+
const listResponse = await client.listResources()
|
|
32
|
+
|
|
33
|
+
expect(listResponse.resources).toBeDefined()
|
|
34
|
+
expect(Array.isArray(listResponse.resources)).toBe(true)
|
|
35
|
+
expect(listResponse.resources.length).toBeGreaterThan(0)
|
|
36
|
+
|
|
37
|
+
const uris = listResponse.resources.map((r) => r.uri)
|
|
38
|
+
expect(uris).toContain('memory://health')
|
|
39
|
+
expect(uris).toContain('memory://briefing')
|
|
40
|
+
expect(uris).toContain('memory://recent')
|
|
41
|
+
expect(uris).toContain('memory://statistics')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('should read memory://health resource', async () => {
|
|
45
|
+
const response = await client.readResource({ uri: 'memory://health' })
|
|
46
|
+
|
|
47
|
+
expect(response.contents).toBeDefined()
|
|
48
|
+
expect(response.contents.length).toBeGreaterThan(0)
|
|
49
|
+
|
|
50
|
+
const text = response.contents[0]!.text as string
|
|
51
|
+
const parsed = JSON.parse(text)
|
|
52
|
+
expect(parsed).toHaveProperty('database')
|
|
53
|
+
expect(parsed).toHaveProperty('vectorIndex')
|
|
54
|
+
expect(parsed).toHaveProperty('scheduler')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('should read memory://briefing resource', async () => {
|
|
58
|
+
const response = await client.readResource({ uri: 'memory://briefing' })
|
|
59
|
+
|
|
60
|
+
expect(response.contents).toBeDefined()
|
|
61
|
+
expect(response.contents.length).toBeGreaterThan(0)
|
|
62
|
+
|
|
63
|
+
const text = response.contents[0]!.text as string
|
|
64
|
+
const parsed = JSON.parse(text)
|
|
65
|
+
expect(parsed).toHaveProperty('userMessage')
|
|
66
|
+
expect(parsed).toHaveProperty('journal')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('should read memory://recent resource', async () => {
|
|
70
|
+
const response = await client.readResource({ uri: 'memory://recent' })
|
|
71
|
+
|
|
72
|
+
expect(response.contents).toBeDefined()
|
|
73
|
+
expect(response.contents.length).toBeGreaterThan(0)
|
|
74
|
+
|
|
75
|
+
const text = response.contents[0]!.text as string
|
|
76
|
+
const parsed = JSON.parse(text)
|
|
77
|
+
// May be empty if no entries exist, but should be valid JSON
|
|
78
|
+
expect(parsed).toBeDefined()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('should read memory://statistics resource', async () => {
|
|
82
|
+
const response = await client.readResource({ uri: 'memory://statistics' })
|
|
83
|
+
|
|
84
|
+
expect(response.contents).toBeDefined()
|
|
85
|
+
expect(response.contents.length).toBeGreaterThan(0)
|
|
86
|
+
|
|
87
|
+
const text = response.contents[0]!.text as string
|
|
88
|
+
const parsed = JSON.parse(text)
|
|
89
|
+
expect(parsed).toHaveProperty('totalEntries')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('should list resource templates', async () => {
|
|
93
|
+
const response = await client.listResourceTemplates()
|
|
94
|
+
|
|
95
|
+
expect(response.resourceTemplates).toBeDefined()
|
|
96
|
+
expect(Array.isArray(response.resourceTemplates)).toBe(true)
|
|
97
|
+
expect(response.resourceTemplates.length).toBeGreaterThan(0)
|
|
98
|
+
|
|
99
|
+
const uriTemplates = response.resourceTemplates.map((t) => t.uriTemplate)
|
|
100
|
+
expect(uriTemplates).toContain('memory://issues/{issue_number}/entries')
|
|
101
|
+
expect(uriTemplates).toContain('memory://kanban/{project_number}')
|
|
102
|
+
})
|
|
103
|
+
})
|