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.
Files changed (291) hide show
  1. package/.github/workflows/codeql.yml +1 -6
  2. package/.github/workflows/docker-publish.yml +15 -49
  3. package/.github/workflows/lint-and-test.yml +1 -1
  4. package/.github/workflows/secrets-scanning.yml +4 -3
  5. package/.github/workflows/security-update.yml +3 -3
  6. package/CHANGELOG.md +213 -0
  7. package/CONTRIBUTING.md +132 -97
  8. package/DOCKER_README.md +184 -235
  9. package/Dockerfile +27 -24
  10. package/README.md +218 -190
  11. package/SECURITY.md +27 -35
  12. package/dist/cli.js +16 -1
  13. package/dist/cli.js.map +1 -1
  14. package/dist/constants/ServerInstructions.d.ts +5 -1
  15. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  16. package/dist/constants/ServerInstructions.js +133 -73
  17. package/dist/constants/ServerInstructions.js.map +1 -1
  18. package/dist/constants/icons.d.ts +2 -2
  19. package/dist/constants/icons.d.ts.map +1 -1
  20. package/dist/constants/icons.js +7 -6
  21. package/dist/constants/icons.js.map +1 -1
  22. package/dist/database/SqliteAdapter.d.ts +37 -24
  23. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  24. package/dist/database/SqliteAdapter.js +319 -157
  25. package/dist/database/SqliteAdapter.js.map +1 -1
  26. package/dist/database/schema.d.ts +45 -0
  27. package/dist/database/schema.d.ts.map +1 -0
  28. package/dist/database/schema.js +92 -0
  29. package/dist/database/schema.js.map +1 -0
  30. package/dist/filtering/ToolFilter.d.ts +1 -1
  31. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  32. package/dist/filtering/ToolFilter.js +13 -2
  33. package/dist/filtering/ToolFilter.js.map +1 -1
  34. package/dist/github/GitHubIntegration.d.ts.map +1 -1
  35. package/dist/github/GitHubIntegration.js +1 -3
  36. package/dist/github/GitHubIntegration.js.map +1 -1
  37. package/dist/handlers/prompts/github.d.ts +12 -0
  38. package/dist/handlers/prompts/github.d.ts.map +1 -0
  39. package/dist/handlers/prompts/github.js +178 -0
  40. package/dist/handlers/prompts/github.js.map +1 -0
  41. package/dist/handlers/prompts/index.d.ts +23 -2
  42. package/dist/handlers/prompts/index.d.ts.map +1 -1
  43. package/dist/handlers/prompts/index.js +7 -432
  44. package/dist/handlers/prompts/index.js.map +1 -1
  45. package/dist/handlers/prompts/workflow.d.ts +12 -0
  46. package/dist/handlers/prompts/workflow.d.ts.map +1 -0
  47. package/dist/handlers/prompts/workflow.js +277 -0
  48. package/dist/handlers/prompts/workflow.js.map +1 -0
  49. package/dist/handlers/resources/core.d.ts +11 -0
  50. package/dist/handlers/resources/core.d.ts.map +1 -0
  51. package/dist/handlers/resources/core.js +433 -0
  52. package/dist/handlers/resources/core.js.map +1 -0
  53. package/dist/handlers/resources/github.d.ts +11 -0
  54. package/dist/handlers/resources/github.d.ts.map +1 -0
  55. package/dist/handlers/resources/github.js +314 -0
  56. package/dist/handlers/resources/github.js.map +1 -0
  57. package/dist/handlers/resources/graph.d.ts +11 -0
  58. package/dist/handlers/resources/graph.d.ts.map +1 -0
  59. package/dist/handlers/resources/graph.js +204 -0
  60. package/dist/handlers/resources/graph.js.map +1 -0
  61. package/dist/handlers/resources/index.d.ts +5 -20
  62. package/dist/handlers/resources/index.d.ts.map +1 -1
  63. package/dist/handlers/resources/index.js +16 -1278
  64. package/dist/handlers/resources/index.js.map +1 -1
  65. package/dist/handlers/resources/shared.d.ts +60 -0
  66. package/dist/handlers/resources/shared.d.ts.map +1 -0
  67. package/dist/handlers/resources/shared.js +49 -0
  68. package/dist/handlers/resources/shared.js.map +1 -0
  69. package/dist/handlers/resources/team.d.ts +13 -0
  70. package/dist/handlers/resources/team.d.ts.map +1 -0
  71. package/dist/handlers/resources/team.js +119 -0
  72. package/dist/handlers/resources/team.js.map +1 -0
  73. package/dist/handlers/resources/templates.d.ts +13 -0
  74. package/dist/handlers/resources/templates.d.ts.map +1 -0
  75. package/dist/handlers/resources/templates.js +310 -0
  76. package/dist/handlers/resources/templates.js.map +1 -0
  77. package/dist/handlers/tools/admin.d.ts +8 -0
  78. package/dist/handlers/tools/admin.d.ts.map +1 -0
  79. package/dist/handlers/tools/admin.js +270 -0
  80. package/dist/handlers/tools/admin.js.map +1 -0
  81. package/dist/handlers/tools/analytics.d.ts +8 -0
  82. package/dist/handlers/tools/analytics.d.ts.map +1 -0
  83. package/dist/handlers/tools/analytics.js +256 -0
  84. package/dist/handlers/tools/analytics.js.map +1 -0
  85. package/dist/handlers/tools/backup.d.ts +8 -0
  86. package/dist/handlers/tools/backup.d.ts.map +1 -0
  87. package/dist/handlers/tools/backup.js +224 -0
  88. package/dist/handlers/tools/backup.js.map +1 -0
  89. package/dist/handlers/tools/core.d.ts +9 -0
  90. package/dist/handlers/tools/core.d.ts.map +1 -0
  91. package/dist/handlers/tools/core.js +326 -0
  92. package/dist/handlers/tools/core.js.map +1 -0
  93. package/dist/handlers/tools/export.d.ts +8 -0
  94. package/dist/handlers/tools/export.d.ts.map +1 -0
  95. package/dist/handlers/tools/export.js +89 -0
  96. package/dist/handlers/tools/export.js.map +1 -0
  97. package/dist/handlers/tools/github/helpers.d.ts +34 -0
  98. package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
  99. package/dist/handlers/tools/github/helpers.js +52 -0
  100. package/dist/handlers/tools/github/helpers.js.map +1 -0
  101. package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
  102. package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
  103. package/dist/handlers/tools/github/insights-tools.js +104 -0
  104. package/dist/handlers/tools/github/insights-tools.js.map +1 -0
  105. package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
  106. package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
  107. package/dist/handlers/tools/github/issue-tools.js +359 -0
  108. package/dist/handlers/tools/github/issue-tools.js.map +1 -0
  109. package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
  110. package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
  111. package/dist/handlers/tools/github/kanban-tools.js +108 -0
  112. package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
  113. package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
  114. package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
  115. package/dist/handlers/tools/github/milestone-tools.js +302 -0
  116. package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
  117. package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
  118. package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
  119. package/dist/handlers/tools/github/mutation-tools.js +15 -0
  120. package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
  121. package/dist/handlers/tools/github/read-tools.d.ts +8 -0
  122. package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
  123. package/dist/handlers/tools/github/read-tools.js +260 -0
  124. package/dist/handlers/tools/github/read-tools.js.map +1 -0
  125. package/dist/handlers/tools/github/schemas.d.ts +467 -0
  126. package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
  127. package/dist/handlers/tools/github/schemas.js +335 -0
  128. package/dist/handlers/tools/github/schemas.js.map +1 -0
  129. package/dist/handlers/tools/github.d.ts +14 -0
  130. package/dist/handlers/tools/github.d.ts.map +1 -0
  131. package/dist/handlers/tools/github.js +28 -0
  132. package/dist/handlers/tools/github.js.map +1 -0
  133. package/dist/handlers/tools/index.d.ts +15 -20
  134. package/dist/handlers/tools/index.d.ts.map +1 -1
  135. package/dist/handlers/tools/index.js +117 -2909
  136. package/dist/handlers/tools/index.js.map +1 -1
  137. package/dist/handlers/tools/relationships.d.ts +8 -0
  138. package/dist/handlers/tools/relationships.d.ts.map +1 -0
  139. package/dist/handlers/tools/relationships.js +308 -0
  140. package/dist/handlers/tools/relationships.js.map +1 -0
  141. package/dist/handlers/tools/schemas.d.ts +108 -0
  142. package/dist/handlers/tools/schemas.d.ts.map +1 -0
  143. package/dist/handlers/tools/schemas.js +122 -0
  144. package/dist/handlers/tools/schemas.js.map +1 -0
  145. package/dist/handlers/tools/search.d.ts +8 -0
  146. package/dist/handlers/tools/search.d.ts.map +1 -0
  147. package/dist/handlers/tools/search.js +282 -0
  148. package/dist/handlers/tools/search.js.map +1 -0
  149. package/dist/handlers/tools/team.d.ts +11 -0
  150. package/dist/handlers/tools/team.d.ts.map +1 -0
  151. package/dist/handlers/tools/team.js +239 -0
  152. package/dist/handlers/tools/team.js.map +1 -0
  153. package/dist/server/McpServer.d.ts +4 -0
  154. package/dist/server/McpServer.d.ts.map +1 -1
  155. package/dist/server/McpServer.js +48 -297
  156. package/dist/server/McpServer.js.map +1 -1
  157. package/dist/server/Scheduler.d.ts +91 -0
  158. package/dist/server/Scheduler.d.ts.map +1 -0
  159. package/dist/server/Scheduler.js +201 -0
  160. package/dist/server/Scheduler.js.map +1 -0
  161. package/dist/transports/http.d.ts +66 -0
  162. package/dist/transports/http.d.ts.map +1 -0
  163. package/dist/transports/http.js +519 -0
  164. package/dist/transports/http.js.map +1 -0
  165. package/dist/types/entities.d.ts +101 -0
  166. package/dist/types/entities.d.ts.map +1 -0
  167. package/dist/types/entities.js +5 -0
  168. package/dist/types/entities.js.map +1 -0
  169. package/dist/types/filtering.d.ts +34 -0
  170. package/dist/types/filtering.d.ts.map +1 -0
  171. package/dist/types/filtering.js +5 -0
  172. package/dist/types/filtering.js.map +1 -0
  173. package/dist/types/github.d.ts +166 -0
  174. package/dist/types/github.d.ts.map +1 -0
  175. package/dist/types/github.js +5 -0
  176. package/dist/types/github.js.map +1 -0
  177. package/dist/types/index.d.ts +35 -292
  178. package/dist/types/index.d.ts.map +1 -1
  179. package/dist/types/index.js +2 -2
  180. package/dist/types/index.js.map +1 -1
  181. package/dist/utils/error-helpers.d.ts +37 -0
  182. package/dist/utils/error-helpers.d.ts.map +1 -0
  183. package/dist/utils/error-helpers.js +47 -0
  184. package/dist/utils/error-helpers.js.map +1 -0
  185. package/dist/utils/logger.d.ts.map +1 -1
  186. package/dist/utils/logger.js +6 -3
  187. package/dist/utils/logger.js.map +1 -1
  188. package/dist/utils/security-utils.d.ts +0 -21
  189. package/dist/utils/security-utils.d.ts.map +1 -1
  190. package/dist/utils/security-utils.js +0 -47
  191. package/dist/utils/security-utils.js.map +1 -1
  192. package/dist/vector/VectorSearchManager.d.ts.map +1 -1
  193. package/dist/vector/VectorSearchManager.js +9 -32
  194. package/dist/vector/VectorSearchManager.js.map +1 -1
  195. package/docker-compose.yml +11 -2
  196. package/hooks/README.md +107 -0
  197. package/hooks/cursor/hooks.json +10 -0
  198. package/hooks/cursor/memory-journal.mdc +22 -0
  199. package/hooks/cursor/session-end.sh +19 -0
  200. package/hooks/kilo-code/session-end-mode.json +11 -0
  201. package/hooks/kiro/session-end.md +13 -0
  202. package/mcp-config-example.json +1 -0
  203. package/package.json +11 -9
  204. package/playwright.config.ts +29 -0
  205. package/releases/v4.5.0.md +116 -0
  206. package/releases/v5.0.0.md +105 -0
  207. package/scripts/generate-server-instructions.ts +176 -0
  208. package/scripts/server-instructions-function-body.ts +77 -0
  209. package/server.json +3 -3
  210. package/src/cli.ts +45 -1
  211. package/src/constants/ServerInstructions.ts +133 -73
  212. package/src/constants/icons.ts +8 -7
  213. package/src/constants/server-instructions.md +268 -0
  214. package/src/database/SqliteAdapter.ts +358 -192
  215. package/src/database/schema.ts +125 -0
  216. package/src/filtering/ToolFilter.ts +13 -2
  217. package/src/github/GitHubIntegration.ts +1 -3
  218. package/src/handlers/prompts/github.ts +209 -0
  219. package/src/handlers/prompts/index.ts +10 -499
  220. package/src/handlers/prompts/workflow.ts +314 -0
  221. package/src/handlers/resources/core.ts +528 -0
  222. package/src/handlers/resources/github.ts +358 -0
  223. package/src/handlers/resources/graph.ts +254 -0
  224. package/src/handlers/resources/index.ts +23 -1570
  225. package/src/handlers/resources/shared.ts +103 -0
  226. package/src/handlers/resources/team.ts +133 -0
  227. package/src/handlers/resources/templates.ts +374 -0
  228. package/src/handlers/tools/admin.ts +285 -0
  229. package/src/handlers/tools/analytics.ts +301 -0
  230. package/src/handlers/tools/backup.ts +242 -0
  231. package/src/handlers/tools/core.ts +350 -0
  232. package/src/handlers/tools/export.ts +115 -0
  233. package/src/handlers/tools/github/helpers.ts +86 -0
  234. package/src/handlers/tools/github/insights-tools.ts +119 -0
  235. package/src/handlers/tools/github/issue-tools.ts +439 -0
  236. package/src/handlers/tools/github/kanban-tools.ts +134 -0
  237. package/src/handlers/tools/github/milestone-tools.ts +392 -0
  238. package/src/handlers/tools/github/mutation-tools.ts +17 -0
  239. package/src/handlers/tools/github/read-tools.ts +328 -0
  240. package/src/handlers/tools/github/schemas.ts +369 -0
  241. package/src/handlers/tools/github.ts +36 -0
  242. package/src/handlers/tools/index.ts +144 -3325
  243. package/src/handlers/tools/relationships.ts +358 -0
  244. package/src/handlers/tools/schemas.ts +132 -0
  245. package/src/handlers/tools/search.ts +343 -0
  246. package/src/handlers/tools/team.ts +273 -0
  247. package/src/server/McpServer.ts +63 -358
  248. package/src/server/Scheduler.ts +278 -0
  249. package/src/transports/http.ts +635 -0
  250. package/src/types/entities.ts +145 -0
  251. package/src/types/filtering.ts +54 -0
  252. package/src/types/github.ts +180 -0
  253. package/src/types/index.ts +67 -375
  254. package/src/utils/error-helpers.ts +52 -0
  255. package/src/utils/logger.ts +6 -3
  256. package/src/utils/security-utils.ts +0 -52
  257. package/src/vector/VectorSearchManager.ts +9 -33
  258. package/tests/constants/icons.test.ts +1 -2
  259. package/tests/constants/server-instructions.test.ts +30 -4
  260. package/tests/database/sqlite-adapter.test.ts +91 -7
  261. package/tests/e2e/auth.spec.ts +154 -0
  262. package/tests/e2e/health.spec.ts +63 -0
  263. package/tests/e2e/protocols.spec.ts +134 -0
  264. package/tests/e2e/resources.spec.ts +103 -0
  265. package/tests/e2e/scheduler.spec.ts +79 -0
  266. package/tests/e2e/security.spec.ts +91 -0
  267. package/tests/e2e/sessions.spec.ts +95 -0
  268. package/tests/e2e/stateless.spec.ts +121 -0
  269. package/tests/e2e/tools.spec.ts +111 -0
  270. package/tests/filtering/tool-filter.test.ts +46 -0
  271. package/tests/handlers/error-path-coverage.test.ts +324 -0
  272. package/tests/handlers/github-resource-handlers.test.ts +453 -0
  273. package/tests/handlers/github-tool-handlers.test.ts +899 -0
  274. package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
  275. package/tests/handlers/prompt-handlers.test.ts +40 -0
  276. package/tests/handlers/resource-handler-coverage.test.ts +181 -0
  277. package/tests/handlers/resource-handlers.test.ts +33 -9
  278. package/tests/handlers/search-tool-handlers.test.ts +272 -0
  279. package/tests/handlers/targeted-gap-closure.test.ts +387 -0
  280. package/tests/handlers/team-resource-handlers.test.ts +156 -0
  281. package/tests/handlers/team-tool-handlers.test.ts +301 -0
  282. package/tests/handlers/tool-handler-coverage.test.ts +469 -0
  283. package/tests/handlers/tool-handlers.test.ts +2 -2
  284. package/tests/security/sql-injection.test.ts +3 -54
  285. package/tests/server/mcp-server.test.ts +503 -8
  286. package/tests/server/scheduler.test.ts +400 -0
  287. package/tests/transports/http-transport.test.ts +620 -0
  288. package/tests/vector/vector-search-manager.test.ts +60 -0
  289. package/vitest.config.ts +4 -1
  290. package/.memory-journal-team.db +0 -0
  291. package/.vscode/settings.json +0 -84
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Memory Journal MCP Server - Resource Shared Types & Helpers
3
+ *
4
+ * Shared types, helpers, and utilities used by all resource group modules.
5
+ */
6
+
7
+ import type { SqliteAdapter } from '../../database/SqliteAdapter.js'
8
+ import type { VectorSearchManager } from '../../vector/VectorSearchManager.js'
9
+ import type { ToolFilterConfig } from '../../filtering/ToolFilter.js'
10
+ import type { McpIcon } from '../../types/index.js'
11
+ import type { GitHubIntegration } from '../../github/GitHubIntegration.js'
12
+ import type { Scheduler } from '../../server/Scheduler.js'
13
+
14
+ /**
15
+ * Resource context for handlers that need extended access
16
+ */
17
+ export interface ResourceContext {
18
+ db: SqliteAdapter
19
+ teamDb?: SqliteAdapter
20
+ vectorManager?: VectorSearchManager
21
+ filterConfig?: ToolFilterConfig | null
22
+ github?: GitHubIntegration | null
23
+ scheduler?: Scheduler | null
24
+ }
25
+
26
+ /**
27
+ * Resource handler result with optional annotations for MCP 2025-11-25
28
+ */
29
+ export interface ResourceResult {
30
+ data: unknown
31
+ annotations?: {
32
+ lastModified?: string // ISO 8601 timestamp
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Internal resource definition with db handler
38
+ */
39
+ export interface InternalResourceDef {
40
+ uri: string
41
+ name: string
42
+ title: string
43
+ description: string
44
+ mimeType: string
45
+ icons?: McpIcon[] // MCP 2025-11-25 icons
46
+ annotations?: {
47
+ audience?: ('user' | 'assistant')[]
48
+ priority?: number
49
+ lastModified?: string
50
+ autoRead?: boolean
51
+ sessionInit?: boolean
52
+ }
53
+ handler: (uri: string, context: ResourceContext) => unknown
54
+ }
55
+
56
+ /**
57
+ * Execute a raw SQL query on the database
58
+ */
59
+ export function execQuery(
60
+ db: SqliteAdapter,
61
+ sql: string,
62
+ params: unknown[] = []
63
+ ): Record<string, unknown>[] {
64
+ const rawDb = db.getRawDb()
65
+ const result = rawDb.exec(sql, params)
66
+ if (result.length === 0) return []
67
+
68
+ const columns = result[0]?.columns ?? []
69
+ return (result[0]?.values ?? []).map((values: unknown[]) => {
70
+ const obj: Record<string, unknown> = {}
71
+ columns.forEach((col: string, i: number) => {
72
+ obj[col] = values[i]
73
+ })
74
+ return obj
75
+ })
76
+ }
77
+
78
+ /**
79
+ * Transform snake_case SQL row to camelCase entry object
80
+ * Ensures consistency with SqliteAdapter.getRecentEntries() output
81
+ */
82
+ export function transformEntryRow(row: Record<string, unknown>): Record<string, unknown> {
83
+ return {
84
+ id: row['id'],
85
+ entryType: row['entry_type'],
86
+ content: row['content'],
87
+ timestamp: row['timestamp'],
88
+ isPersonal: row['is_personal'] === 1 || row['is_personal'] === true,
89
+ significanceType: row['significance_type'] ?? null,
90
+ autoContext: row['auto_context'] ?? null,
91
+ deletedAt: row['deleted_at'] ?? null,
92
+ projectNumber: row['project_number'] ?? null,
93
+ projectOwner: row['project_owner'] ?? null,
94
+ issueNumber: row['issue_number'] ?? null,
95
+ issueUrl: row['issue_url'] ?? null,
96
+ prNumber: row['pr_number'] ?? null,
97
+ prUrl: row['pr_url'] ?? null,
98
+ prStatus: row['pr_status'] ?? null,
99
+ workflowRunId: row['workflow_run_id'] ?? null,
100
+ workflowName: row['workflow_name'] ?? null,
101
+ workflowStatus: row['workflow_status'] ?? null,
102
+ }
103
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Team Resource Definitions - 2 resources
3
+ *
4
+ * Resources: memory://team/recent, memory://team/statistics
5
+ *
6
+ * Requires TEAM_DB_PATH to be configured.
7
+ */
8
+
9
+ import { ICON_CLOCK, ICON_TEAM } from '../../constants/icons.js'
10
+ import type { InternalResourceDef, ResourceContext, ResourceResult } from './shared.js'
11
+
12
+ // ============================================================================
13
+ // Helpers
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Enrich entries with author column from the team database
18
+ */
19
+ function enrichWithAuthor<T extends { id: number }>(
20
+ entries: T[],
21
+ context: ResourceContext
22
+ ): (T & { author: string | null })[] {
23
+ if (!context.teamDb) return entries.map((e) => ({ ...e, author: null }))
24
+ const rawDb = context.teamDb.getRawDb()
25
+ return entries.map((e) => {
26
+ const authorResult = rawDb.exec('SELECT author FROM memory_journal WHERE id = ?', [e.id])
27
+ const author = (authorResult[0]?.values[0]?.[0] as string) ?? null
28
+ return { ...e, author }
29
+ })
30
+ }
31
+
32
+ // ============================================================================
33
+ // Resource Definitions
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Get team resource definitions
38
+ */
39
+ export function getTeamResourceDefinitions(): InternalResourceDef[] {
40
+ return [
41
+ {
42
+ uri: 'memory://team/recent',
43
+ name: 'Recent Team Entries',
44
+ title: 'Recent Team-Shared Entries',
45
+ description:
46
+ 'Recent entries from the team database. Requires TEAM_DB_PATH configuration.',
47
+ mimeType: 'application/json',
48
+ icons: [ICON_CLOCK],
49
+ annotations: {
50
+ audience: ['assistant'],
51
+ priority: 0.7,
52
+ },
53
+ handler: (_uri: string, context: ResourceContext): ResourceResult => {
54
+ if (!context.teamDb) {
55
+ return {
56
+ data: {
57
+ error: 'Team database not configured. Set TEAM_DB_PATH to enable.',
58
+ entries: [],
59
+ count: 0,
60
+ },
61
+ }
62
+ }
63
+
64
+ const entries = context.teamDb.getRecentEntries(10)
65
+ const lastModified = entries[0]?.timestamp ?? new Date().toISOString()
66
+ const enriched = enrichWithAuthor(entries, context)
67
+
68
+ return {
69
+ data: {
70
+ entries: enriched,
71
+ count: enriched.length,
72
+ source: 'team',
73
+ },
74
+ annotations: { lastModified },
75
+ }
76
+ },
77
+ },
78
+ {
79
+ uri: 'memory://team/statistics',
80
+ name: 'Team Statistics',
81
+ title: 'Team Database Statistics',
82
+ description: 'Entry counts, types, and contributor breakdown for the team database.',
83
+ mimeType: 'application/json',
84
+ icons: [ICON_TEAM],
85
+ annotations: {
86
+ audience: ['assistant'],
87
+ priority: 0.6,
88
+ },
89
+ handler: (_uri: string, context: ResourceContext): ResourceResult => {
90
+ if (!context.teamDb) {
91
+ return {
92
+ data: {
93
+ error: 'Team database not configured. Set TEAM_DB_PATH to enable.',
94
+ configured: false,
95
+ },
96
+ }
97
+ }
98
+
99
+ const stats = context.teamDb.getStatistics('week')
100
+ const rawDb = context.teamDb.getRawDb()
101
+
102
+ // Author breakdown
103
+ let authors: { author: string; count: number }[] = []
104
+ try {
105
+ const authorResult = rawDb.exec(
106
+ `SELECT COALESCE(author, 'unknown') as author, COUNT(*) as count
107
+ FROM memory_journal
108
+ WHERE deleted_at IS NULL
109
+ GROUP BY COALESCE(author, 'unknown')
110
+ ORDER BY count DESC`
111
+ )
112
+ if (authorResult[0]) {
113
+ authors = authorResult[0].values.map((row) => ({
114
+ author: row[0] as string,
115
+ count: row[1] as number,
116
+ }))
117
+ }
118
+ } catch {
119
+ // Author column may not exist yet
120
+ }
121
+
122
+ return {
123
+ data: {
124
+ configured: true,
125
+ ...stats,
126
+ authors,
127
+ source: 'team',
128
+ },
129
+ }
130
+ },
131
+ },
132
+ ]
133
+ }
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Memory Journal MCP Server - Template Resource Definitions
3
+ *
4
+ * Resources with URI templates (parameterized):
5
+ * projects/{number}/timeline, issues/{issue_number}/entries, prs/{pr_number}/entries,
6
+ * prs/{pr_number}/timeline, kanban/{project_number}, kanban/{project_number}/diagram
7
+ */
8
+
9
+ import { ICON_ISSUE, ICON_PR } from '../../constants/icons.js'
10
+ import type { InternalResourceDef, ResourceContext } from './shared.js'
11
+ import { execQuery, transformEntryRow } from './shared.js'
12
+
13
+ /**
14
+ * Get template resource definitions
15
+ */
16
+ export function getTemplateResourceDefinitions(): InternalResourceDef[] {
17
+ return [
18
+ {
19
+ uri: 'memory://projects/{number}/timeline',
20
+ name: 'Project Timeline',
21
+ title: 'Project Activity Timeline',
22
+ description: 'Project activity timeline',
23
+ mimeType: 'application/json',
24
+ annotations: {
25
+ audience: ['assistant'],
26
+ priority: 0.6,
27
+ },
28
+ handler: (uri: string, context: ResourceContext) => {
29
+ const match = /memory:\/\/projects\/(\d+)\/timeline/.exec(uri)
30
+ const projectNumber = match?.[1] ? parseInt(match[1], 10) : null
31
+
32
+ if (projectNumber === null) {
33
+ return { error: 'Invalid project number' }
34
+ }
35
+
36
+ const rows = execQuery(
37
+ context.db,
38
+ `
39
+ SELECT * FROM memory_journal
40
+ WHERE project_number = ?
41
+ AND deleted_at IS NULL
42
+ ORDER BY timestamp DESC
43
+ LIMIT 50
44
+ `,
45
+ [projectNumber]
46
+ )
47
+ const entries = rows.map(transformEntryRow)
48
+ return { projectNumber, entries, count: entries.length }
49
+ },
50
+ },
51
+ {
52
+ uri: 'memory://issues/{issue_number}/entries',
53
+ name: 'Issue Entries',
54
+ title: 'Entries Linked to Issue',
55
+ description: 'All entries linked to a specific issue',
56
+ mimeType: 'application/json',
57
+ icons: [ICON_ISSUE],
58
+ annotations: {
59
+ audience: ['assistant'],
60
+ priority: 0.6,
61
+ },
62
+ handler: (uri: string, context: ResourceContext) => {
63
+ const match = /memory:\/\/issues\/(\d+)\/entries/.exec(uri)
64
+ const issueNumber = match?.[1] ? parseInt(match[1], 10) : null
65
+
66
+ if (issueNumber === null) {
67
+ return { error: 'Invalid issue number' }
68
+ }
69
+
70
+ const rows = execQuery(
71
+ context.db,
72
+ `
73
+ SELECT * FROM memory_journal
74
+ WHERE issue_number = ?
75
+ AND deleted_at IS NULL
76
+ ORDER BY timestamp DESC
77
+ `,
78
+ [issueNumber]
79
+ )
80
+ const entries = rows.map(transformEntryRow)
81
+ return { issueNumber, entries, count: entries.length }
82
+ },
83
+ },
84
+ {
85
+ uri: 'memory://prs/{pr_number}/entries',
86
+ name: 'PR Entries',
87
+ title: 'Entries Linked to PR',
88
+ description: 'All entries linked to a specific pull request',
89
+ mimeType: 'application/json',
90
+ icons: [ICON_PR],
91
+ annotations: {
92
+ audience: ['assistant'],
93
+ priority: 0.6,
94
+ },
95
+ handler: (uri: string, context: ResourceContext) => {
96
+ const match = /memory:\/\/prs\/(\d+)\/entries/.exec(uri)
97
+ const prNumber = match?.[1] ? parseInt(match[1], 10) : null
98
+
99
+ if (prNumber === null) {
100
+ return { error: 'Invalid PR number' }
101
+ }
102
+
103
+ const rows = execQuery(
104
+ context.db,
105
+ `
106
+ SELECT * FROM memory_journal
107
+ WHERE pr_number = ?
108
+ AND deleted_at IS NULL
109
+ ORDER BY timestamp DESC
110
+ `,
111
+ [prNumber]
112
+ )
113
+ const entries = rows.map(transformEntryRow)
114
+ return {
115
+ prNumber,
116
+ entries,
117
+ count: entries.length,
118
+ ...(entries.length === 0
119
+ ? {
120
+ hint: 'No journal entries linked to this PR. Use create_entry with pr_number to link entries.',
121
+ }
122
+ : {}),
123
+ }
124
+ },
125
+ },
126
+ {
127
+ uri: 'memory://prs/{pr_number}/timeline',
128
+ name: 'PR Timeline',
129
+ title: 'Combined PR and Journal Timeline',
130
+ description: 'Combined PR + journal timeline with live PR metadata',
131
+ mimeType: 'application/json',
132
+ icons: [ICON_PR],
133
+ annotations: {
134
+ audience: ['assistant'],
135
+ priority: 0.5,
136
+ },
137
+ handler: async (uri: string, context: ResourceContext) => {
138
+ const match = /memory:\/\/prs\/(\d+)\/timeline/.exec(uri)
139
+ const prNumber = match?.[1] ? parseInt(match[1], 10) : null
140
+
141
+ if (prNumber === null) {
142
+ return { error: 'Invalid PR number' }
143
+ }
144
+
145
+ // Fetch live PR metadata from GitHub if available
146
+ let prMetadata: {
147
+ title: string
148
+ state: string
149
+ draft: boolean
150
+ mergedAt: string | null
151
+ closedAt: string | null
152
+ author: string
153
+ headBranch: string
154
+ baseBranch: string
155
+ } | null = null
156
+
157
+ if (context.github) {
158
+ try {
159
+ const repoInfo = await context.github.getRepoInfo()
160
+ if (repoInfo.owner && repoInfo.repo) {
161
+ const pr = await context.github.getPullRequest(
162
+ repoInfo.owner,
163
+ repoInfo.repo,
164
+ prNumber
165
+ )
166
+ if (pr) {
167
+ prMetadata = {
168
+ title: pr.title,
169
+ state: pr.state,
170
+ draft: pr.draft,
171
+ mergedAt: pr.mergedAt,
172
+ closedAt: pr.closedAt,
173
+ author: pr.author,
174
+ headBranch: pr.headBranch,
175
+ baseBranch: pr.baseBranch,
176
+ }
177
+ }
178
+ }
179
+ } catch {
180
+ // GitHub not available, proceed without metadata
181
+ }
182
+ }
183
+
184
+ const rows = execQuery(
185
+ context.db,
186
+ `
187
+ SELECT * FROM memory_journal
188
+ WHERE pr_number = ?
189
+ AND deleted_at IS NULL
190
+ ORDER BY timestamp DESC
191
+ `,
192
+ [prNumber]
193
+ )
194
+ const entries = rows.map(transformEntryRow)
195
+
196
+ let timelineNote: string
197
+ if (prMetadata) {
198
+ const stateDesc = prMetadata.state.toLowerCase()
199
+ const mergedNote = prMetadata.mergedAt ? ' (merged)' : ''
200
+ const draftNote = prMetadata.draft ? ' [DRAFT]' : ''
201
+ timelineNote = `PR #${String(prNumber)} is ${stateDesc}${mergedNote}${draftNote}`
202
+ } else {
203
+ timelineNote =
204
+ 'GitHub integration unavailable for live PR status. Entry timestamps show journal activity.'
205
+ }
206
+
207
+ return {
208
+ prNumber,
209
+ prMetadata,
210
+ entries,
211
+ count: entries.length,
212
+ timelineNote,
213
+ ...(entries.length === 0
214
+ ? {
215
+ hint: 'No journal entries linked to this PR. Use create_entry with pr_number to link entries.',
216
+ }
217
+ : {}),
218
+ }
219
+ },
220
+ },
221
+ // Kanban board resources (GitHub Projects v2)
222
+ {
223
+ uri: 'memory://kanban/{project_number}',
224
+ name: 'Kanban Board',
225
+ title: 'GitHub Project Kanban Board',
226
+ description: 'View a GitHub Project v2 as a Kanban board with items grouped by Status',
227
+ mimeType: 'application/json',
228
+ annotations: {
229
+ audience: ['assistant'],
230
+ priority: 0.6,
231
+ },
232
+ handler: async (uri: string, context: ResourceContext) => {
233
+ const match = /memory:\/\/kanban\/(\d+)/.exec(uri)
234
+ const projectNumber = match?.[1] ? parseInt(match[1], 10) : null
235
+
236
+ if (projectNumber === null) {
237
+ return { error: 'Invalid project number' }
238
+ }
239
+
240
+ if (!context.github) {
241
+ return {
242
+ error: 'GitHub integration not available',
243
+ hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
244
+ }
245
+ }
246
+
247
+ const repoInfo = await context.github.getRepoInfo()
248
+ const owner = repoInfo.owner
249
+ const repo = repoInfo.repo ?? undefined
250
+
251
+ if (!owner) {
252
+ return {
253
+ error: 'Could not detect repository owner',
254
+ hint: 'Set GITHUB_REPO_PATH to your git repository.',
255
+ }
256
+ }
257
+
258
+ const board = await context.github.getProjectKanban(owner, projectNumber, repo)
259
+ if (!board) {
260
+ return {
261
+ error: `Project #${String(projectNumber)} not found or Status field not configured`,
262
+ projectNumber,
263
+ owner,
264
+ hint: 'Projects can be at user, repository, or organization level.',
265
+ }
266
+ }
267
+
268
+ return board
269
+ },
270
+ },
271
+ {
272
+ uri: 'memory://kanban/{project_number}/diagram',
273
+ name: 'Kanban Diagram',
274
+ title: 'Kanban Board Mermaid Diagram',
275
+ description: 'Mermaid diagram visualization of a GitHub Project Kanban board',
276
+ mimeType: 'text/plain',
277
+ annotations: {
278
+ audience: ['user', 'assistant'],
279
+ priority: 0.5,
280
+ },
281
+ handler: async (uri: string, context: ResourceContext) => {
282
+ const match = /memory:\/\/kanban\/(\d+)\/diagram/.exec(uri)
283
+ const projectNumber = match?.[1] ? parseInt(match[1], 10) : null
284
+
285
+ if (projectNumber === null) {
286
+ return { error: 'Invalid project number' }
287
+ }
288
+
289
+ if (!context.github) {
290
+ return {
291
+ format: 'mermaid',
292
+ diagram: 'graph LR\n NoGitHub[GitHub integration not available]',
293
+ message: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
294
+ }
295
+ }
296
+
297
+ const repoInfo = await context.github.getRepoInfo()
298
+ const owner = repoInfo.owner
299
+ const repo = repoInfo.repo ?? undefined
300
+
301
+ if (!owner) {
302
+ return {
303
+ format: 'mermaid',
304
+ diagram: 'graph LR\n NoOwner[Repository owner not detected]',
305
+ message: 'Set GITHUB_REPO_PATH to your git repository.',
306
+ }
307
+ }
308
+
309
+ const board = await context.github.getProjectKanban(owner, projectNumber, repo)
310
+ if (!board) {
311
+ return {
312
+ format: 'mermaid',
313
+ diagram: `graph LR\n NotFound[Project #${String(projectNumber)} not found]`,
314
+ message: 'Ensure the project exists and has a Status field.',
315
+ }
316
+ }
317
+
318
+ // Build Mermaid diagram with subgraphs for each column
319
+ const lines: string[] = ['graph LR']
320
+
321
+ lines.push(' classDef issue fill:#28a745,color:#fff')
322
+ lines.push(' classDef pr fill:#6f42c1,color:#fff')
323
+ lines.push(' classDef draft fill:#6c757d,color:#fff')
324
+
325
+ for (const column of board.columns) {
326
+ const safeStatus = column.status.replace(/["\s]/g, '_')
327
+ lines.push(
328
+ ` subgraph ${safeStatus}["${column.status} (${String(column.items.length)})"]`
329
+ )
330
+
331
+ for (const item of column.items) {
332
+ const safeId = item.id.replace(/[^a-zA-Z0-9]/g, '').slice(-8)
333
+ const label = item.title.slice(0, 25).replace(/["[\]]/g, "'")
334
+ const typeIcon =
335
+ item.type === 'ISSUE'
336
+ ? '🔵'
337
+ : item.type === 'PULL_REQUEST'
338
+ ? '🟣'
339
+ : '⚪'
340
+ const numberStr =
341
+ item.number !== undefined && item.number !== 0
342
+ ? `#${String(item.number)}`
343
+ : ''
344
+ lines.push(` I${safeId}["${typeIcon} ${numberStr} ${label}..."]`)
345
+
346
+ const typeClass =
347
+ item.type === 'ISSUE'
348
+ ? 'issue'
349
+ : item.type === 'PULL_REQUEST'
350
+ ? 'pr'
351
+ : 'draft'
352
+ lines.push(` class I${safeId} ${typeClass}`)
353
+ }
354
+
355
+ lines.push(' end')
356
+ }
357
+
358
+ return {
359
+ format: 'mermaid',
360
+ diagram: lines.join('\n'),
361
+ projectNumber,
362
+ projectTitle: board.projectTitle,
363
+ columnCount: board.columns.length,
364
+ totalItems: board.totalItems,
365
+ legend: {
366
+ '🔵': 'Issue',
367
+ '🟣': 'Pull Request',
368
+ '⚪': 'Draft Issue',
369
+ },
370
+ }
371
+ },
372
+ },
373
+ ]
374
+ }