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,242 @@
1
+ /**
2
+ * Backup Tool Group - 4 tools
3
+ *
4
+ * Tools: backup_journal, list_backups, restore_backup, cleanup_backups
5
+ */
6
+
7
+ import { z } from 'zod'
8
+ import type { ToolDefinition, ToolContext } from '../../types/index.js'
9
+ import { formatHandlerError } from '../../utils/error-helpers.js'
10
+ import { sendProgress } from '../../utils/progress-utils.js'
11
+
12
+ // ============================================================================
13
+ // Output Schemas
14
+ // ============================================================================
15
+
16
+ const BackupResultOutputSchema = z.object({
17
+ success: z.boolean(),
18
+ message: z.string().optional(),
19
+ filename: z.string().optional(),
20
+ path: z.string().optional(),
21
+ sizeBytes: z.number().optional(),
22
+ error: z.string().optional(),
23
+ })
24
+
25
+ const BackupInfoSchema = z.object({
26
+ filename: z.string(),
27
+ sizeBytes: z.number(),
28
+ createdAt: z.string(),
29
+ })
30
+
31
+ const BackupsListOutputSchema = z.object({
32
+ success: z.boolean().optional(),
33
+ backups: z.array(BackupInfoSchema).optional(),
34
+ total: z.number().optional(),
35
+ backupsDirectory: z.string().optional(),
36
+ hint: z.string().optional(),
37
+ error: z.string().optional(),
38
+ })
39
+
40
+ const RestoreResultOutputSchema = z.object({
41
+ success: z.boolean(),
42
+ message: z.string().optional(),
43
+ restoredFrom: z.string().optional(),
44
+ previousEntryCount: z.number().optional(),
45
+ newEntryCount: z.number().optional(),
46
+ warning: z.string().optional(),
47
+ revertedChanges: z
48
+ .object({
49
+ tagMerges: z.string().optional(),
50
+ entries: z.string().optional(),
51
+ })
52
+ .optional(),
53
+ error: z.string().optional(),
54
+ })
55
+
56
+ const CleanupBackupsOutputSchema = z.object({
57
+ success: z.boolean(),
58
+ deleted: z.array(z.string()).optional(),
59
+ deletedCount: z.number().optional(),
60
+ keptCount: z.number().optional(),
61
+ message: z.string().optional(),
62
+ error: z.string().optional(),
63
+ })
64
+
65
+ // ============================================================================
66
+ // Tool Definitions
67
+ // ============================================================================
68
+
69
+ export function getBackupTools(context: ToolContext): ToolDefinition[] {
70
+ const { db, progress } = context
71
+ return [
72
+ {
73
+ name: 'backup_journal',
74
+ title: 'Backup Journal Database',
75
+ description:
76
+ 'Create a timestamped backup of the journal database. Backups are stored in the backups/ directory.',
77
+ group: 'backup',
78
+ inputSchema: z.object({
79
+ name: z
80
+ .string()
81
+ .optional()
82
+ .describe('Custom backup name (optional, defaults to timestamp)'),
83
+ }),
84
+ outputSchema: BackupResultOutputSchema,
85
+ annotations: { readOnlyHint: false, idempotentHint: true },
86
+ handler: (params: unknown) => {
87
+ try {
88
+ const input = z
89
+ .object({
90
+ name: z.string().optional(),
91
+ })
92
+ .parse(params)
93
+ const result = db.exportToFile(input.name)
94
+ return {
95
+ success: true,
96
+ message: `Backup created successfully`,
97
+ filename: result.filename,
98
+ path: result.path,
99
+ sizeBytes: result.sizeBytes,
100
+ }
101
+ } catch (err) {
102
+ return formatHandlerError(err)
103
+ }
104
+ },
105
+ },
106
+ {
107
+ name: 'list_backups',
108
+ title: 'List Journal Backups',
109
+ description: 'List all available backup files with their sizes and creation dates',
110
+ group: 'backup',
111
+ inputSchema: z.object({}),
112
+ outputSchema: BackupsListOutputSchema,
113
+ annotations: { readOnlyHint: true, idempotentHint: true },
114
+ handler: (_params: unknown) => {
115
+ try {
116
+ const backups = db.listBackups()
117
+ return {
118
+ backups,
119
+ total: backups.length,
120
+ backupsDirectory: db.getBackupsDir(),
121
+ hint:
122
+ backups.length === 0
123
+ ? 'No backups found. Use backup_journal to create one.'
124
+ : undefined,
125
+ }
126
+ } catch (err) {
127
+ return formatHandlerError(err)
128
+ }
129
+ },
130
+ },
131
+ {
132
+ name: 'restore_backup',
133
+ title: 'Restore Journal from Backup',
134
+ description:
135
+ 'Restore the journal database from a backup file. WARNING: This replaces all current data. An automatic backup is created before restore.',
136
+ group: 'backup',
137
+ inputSchema: z.object({
138
+ filename: z
139
+ .string()
140
+ .describe('Backup filename to restore from (e.g., backup_2025-01-01.db)'),
141
+ confirm: z
142
+ .literal(true)
143
+ .describe('Must be set to true to confirm the restore operation'),
144
+ }),
145
+ outputSchema: RestoreResultOutputSchema,
146
+ annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true },
147
+ handler: async (params: unknown) => {
148
+ try {
149
+ const input = z
150
+ .object({
151
+ filename: z.string(),
152
+ confirm: z.literal(true),
153
+ })
154
+ .parse(params)
155
+
156
+ // Capture progress context values BEFORE any async operations
157
+ const progressServer = progress?.server
158
+ const progressTokenValue = progress?.progressToken
159
+
160
+ await sendProgress(progress, 1, 3, 'Preparing restore...')
161
+ await sendProgress(progress, 2, 3, 'Restoring database from backup...')
162
+ const result = await db.restoreFromFile(input.filename)
163
+
164
+ // Send directly using captured primitives (db.restoreFromFile reinitializes DB)
165
+ if (progressServer !== undefined && progressTokenValue !== undefined) {
166
+ try {
167
+ await progressServer.notification({
168
+ method: 'notifications/progress' as const,
169
+ params: {
170
+ progressToken: progressTokenValue,
171
+ progress: 3,
172
+ total: 3,
173
+ message: 'Restore complete',
174
+ },
175
+ })
176
+ } catch {
177
+ // Best-effort notification
178
+ }
179
+ }
180
+
181
+ return {
182
+ success: true,
183
+ message: `Database restored from ${input.filename}`,
184
+ restoredFrom: result.restoredFrom,
185
+ previousEntryCount: result.previousEntryCount,
186
+ newEntryCount: result.newEntryCount,
187
+ warning:
188
+ 'A pre-restore backup was automatically created. Any changes made since this backup (including tag merges, new entries, and relationships) have been reverted.',
189
+ revertedChanges: {
190
+ tagMerges:
191
+ 'Any merge_tags operations since this backup are reverted. Previously merged tags will reappear as separate tags.',
192
+ entries:
193
+ result.previousEntryCount !== result.newEntryCount
194
+ ? `Entry count changed from ${String(result.previousEntryCount)} to ${String(result.newEntryCount)}`
195
+ : undefined,
196
+ },
197
+ }
198
+ } catch (err) {
199
+ return formatHandlerError(err)
200
+ }
201
+ },
202
+ },
203
+ {
204
+ name: 'cleanup_backups',
205
+ title: 'Cleanup Old Backups',
206
+ description:
207
+ 'Delete old backup files, keeping only the most recent N backups. Use list_backups to preview before cleanup.',
208
+ group: 'backup',
209
+ inputSchema: z.object({
210
+ keep_count: z
211
+ .number()
212
+ .min(1)
213
+ .default(5)
214
+ .describe('Number of most recent backups to keep (default: 5)'),
215
+ }),
216
+ outputSchema: CleanupBackupsOutputSchema,
217
+ annotations: { readOnlyHint: false, idempotentHint: false },
218
+ handler: (params: unknown) => {
219
+ try {
220
+ const { keep_count } = z
221
+ .object({ keep_count: z.number().min(1).default(5) })
222
+ .parse(params)
223
+
224
+ const result = db.deleteOldBackups(keep_count)
225
+
226
+ return {
227
+ success: true,
228
+ deleted: result.deleted,
229
+ deletedCount: result.deleted.length,
230
+ keptCount: result.kept,
231
+ message:
232
+ result.deleted.length > 0
233
+ ? `Deleted ${String(result.deleted.length)} old backup(s), kept ${String(result.kept)}`
234
+ : `No backups to delete. Currently have ${String(result.kept)} backup(s).`,
235
+ }
236
+ } catch (err) {
237
+ return formatHandlerError(err)
238
+ }
239
+ },
240
+ },
241
+ ]
242
+ }
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Core Tool Group - 6 tools
3
+ *
4
+ * Tools: create_entry, get_entry_by_id, get_recent_entries,
5
+ * create_entry_minimal, test_simple, list_tags
6
+ */
7
+
8
+ import { z } from 'zod'
9
+ import { execFileSync } from 'node:child_process'
10
+ import type { ToolDefinition, ToolContext } from '../../types/index.js'
11
+ import { formatHandlerError } from '../../utils/error-helpers.js'
12
+ import {
13
+ ENTRY_TYPES,
14
+ SIGNIFICANCE_TYPES,
15
+ EntryOutputSchema,
16
+ EntriesListOutputSchema,
17
+ RelationshipOutputSchema,
18
+ ImportanceBreakdownSchema,
19
+ TagOutputSchema,
20
+ } from './schemas.js'
21
+
22
+ // ============================================================================
23
+ // Input Schemas
24
+ // ============================================================================
25
+
26
+ /** Strict schema — used inside handler for structured Zod errors */
27
+ const CreateEntrySchema = z.object({
28
+ content: z.string().min(1).max(50000),
29
+ entry_type: z.enum(ENTRY_TYPES).optional().default('personal_reflection'),
30
+ tags: z.array(z.string()).optional().default([]),
31
+ is_personal: z.boolean().optional().default(true),
32
+ significance_type: z.enum(SIGNIFICANCE_TYPES).optional(),
33
+ auto_context: z.boolean().optional().default(true),
34
+ project_number: z.number().optional(),
35
+ project_owner: z.string().optional(),
36
+ issue_number: z.number().optional(),
37
+ issue_url: z.string().optional(),
38
+ pr_number: z.number().optional(),
39
+ pr_url: z.string().optional(),
40
+ pr_status: z.enum(['draft', 'open', 'merged', 'closed']).optional(),
41
+ workflow_run_id: z.number().optional(),
42
+ workflow_name: z.string().optional(),
43
+ workflow_status: z.enum(['queued', 'in_progress', 'completed']).optional(),
44
+ share_with_team: z.boolean().optional().default(false),
45
+ })
46
+
47
+ /** Relaxed schema — passed to SDK inputSchema so Zod enum errors reach the handler */
48
+ const CreateEntrySchemaMcp = z.object({
49
+ content: z.string().min(1).max(50000),
50
+ entry_type: z.string().optional().default('personal_reflection'),
51
+ tags: z.array(z.string()).optional().default([]),
52
+ is_personal: z.boolean().optional().default(true),
53
+ significance_type: z.string().optional(),
54
+ auto_context: z.boolean().optional().default(true),
55
+ project_number: z.number().optional(),
56
+ project_owner: z.string().optional(),
57
+ issue_number: z.number().optional(),
58
+ issue_url: z.string().optional(),
59
+ pr_number: z.number().optional(),
60
+ pr_url: z.string().optional(),
61
+ pr_status: z.string().optional(),
62
+ workflow_run_id: z.number().optional(),
63
+ workflow_name: z.string().optional(),
64
+ workflow_status: z.string().optional(),
65
+ share_with_team: z.boolean().optional().default(false),
66
+ })
67
+
68
+ const GetEntryByIdSchema = z.object({
69
+ entry_id: z.number(),
70
+ include_relationships: z.boolean().optional().default(true),
71
+ })
72
+
73
+ const GetRecentEntriesSchema = z.object({
74
+ limit: z.number().max(500).optional().default(5),
75
+ is_personal: z.boolean().optional(),
76
+ })
77
+
78
+ const CreateEntryMinimalSchema = z.object({
79
+ content: z.string().min(1).max(50000),
80
+ })
81
+
82
+ const TestSimpleSchema = z.object({
83
+ message: z.string().optional().default('Hello'),
84
+ })
85
+
86
+ // ============================================================================
87
+ // Output Schemas
88
+ // ============================================================================
89
+
90
+ const CreateEntryOutputSchema = z.object({
91
+ success: z.boolean().optional(),
92
+ entry: EntryOutputSchema.optional(),
93
+ sharedWithTeam: z.boolean().optional(),
94
+ author: z.string().optional(),
95
+ error: z.string().optional(),
96
+ })
97
+
98
+ const EntryByIdOutputSchema = z.object({
99
+ entry: EntryOutputSchema.optional(),
100
+ relationships: z.array(RelationshipOutputSchema).optional(),
101
+ importance: z.number().nullable().optional(),
102
+ importanceBreakdown: ImportanceBreakdownSchema.optional(),
103
+ error: z.string().optional(),
104
+ })
105
+
106
+ const TestSimpleOutputSchema = z.object({
107
+ message: z.string(),
108
+ })
109
+
110
+ const TagsListOutputSchema = z.object({
111
+ tags: z.array(TagOutputSchema).optional(),
112
+ count: z.number().optional(),
113
+ success: z.boolean().optional(),
114
+ error: z.string().optional(),
115
+ })
116
+
117
+ // ============================================================================
118
+ // Helpers
119
+ // ============================================================================
120
+
121
+ /** Resolve the author name for team-shared entries */
122
+ function resolveTeamAuthor(): string {
123
+ const envAuthor = process.env['TEAM_AUTHOR']?.trim().replace(/"/g, '')
124
+ if (envAuthor) return envAuthor
125
+ try {
126
+ const gitUser = execFileSync('git', ['config', 'user.name'], {
127
+ encoding: 'utf-8',
128
+ timeout: 3000,
129
+ })
130
+ .trim()
131
+ .replace(/"/g, '')
132
+ if (gitUser) return gitUser
133
+ } catch {
134
+ // Git not available
135
+ }
136
+ return 'unknown'
137
+ }
138
+
139
+ // ============================================================================
140
+ // Tool Definitions
141
+ // ============================================================================
142
+
143
+ export function getCoreTools(context: ToolContext): ToolDefinition[] {
144
+ const { db, teamDb, vectorManager, github } = context
145
+ return [
146
+ {
147
+ name: 'create_entry',
148
+ title: 'Create Journal Entry',
149
+ description:
150
+ 'Create a new journal entry with context and tags (v2.1.0: GitHub Actions support)',
151
+ group: 'core',
152
+ inputSchema: CreateEntrySchemaMcp,
153
+ outputSchema: CreateEntryOutputSchema,
154
+ annotations: { readOnlyHint: false, idempotentHint: false },
155
+ handler: (params: unknown) => {
156
+ try {
157
+ const input = CreateEntrySchema.parse(params)
158
+
159
+ // Auto-populate issueUrl if issue_number provided without issueUrl
160
+ let resolvedIssueUrl = input.issue_url
161
+ if (input.issue_number !== undefined && !input.issue_url && github) {
162
+ const cachedRepo = github.getCachedRepoInfo()
163
+ if (cachedRepo?.owner && cachedRepo?.repo) {
164
+ resolvedIssueUrl = `https://github.com/${cachedRepo.owner}/${cachedRepo.repo}/issues/${String(input.issue_number)}`
165
+ }
166
+ }
167
+
168
+ const entry = db.createEntry({
169
+ content: input.content,
170
+ entryType: input.entry_type,
171
+ tags: input.tags,
172
+ isPersonal: input.is_personal,
173
+ significanceType: input.significance_type ?? null,
174
+ projectNumber: input.project_number,
175
+ projectOwner: input.project_owner,
176
+ issueNumber: input.issue_number,
177
+ issueUrl: resolvedIssueUrl,
178
+ prNumber: input.pr_number,
179
+ prUrl: input.pr_url,
180
+ prStatus: input.pr_status,
181
+ workflowRunId: input.workflow_run_id,
182
+ workflowName: input.workflow_name,
183
+ workflowStatus: input.workflow_status,
184
+ })
185
+
186
+ // Auto-index to vector store for semantic search (fire-and-forget)
187
+ if (vectorManager) {
188
+ vectorManager.addEntry(entry.id, entry.content).catch(() => {
189
+ // Non-critical failure, entry already saved to DB
190
+ })
191
+ }
192
+
193
+ // Share with team if requested
194
+ let sharedWithTeam = false
195
+ let author: string | undefined
196
+ if (input.share_with_team && teamDb) {
197
+ try {
198
+ author = resolveTeamAuthor()
199
+ const teamEntry = teamDb.createEntry({
200
+ content: input.content,
201
+ entryType: input.entry_type,
202
+ tags: input.tags,
203
+ isPersonal: false,
204
+ significanceType: input.significance_type ?? null,
205
+ autoContext: JSON.stringify({ author }),
206
+ projectNumber: input.project_number,
207
+ projectOwner: input.project_owner,
208
+ issueNumber: input.issue_number,
209
+ issueUrl: resolvedIssueUrl,
210
+ prNumber: input.pr_number,
211
+ prUrl: input.pr_url,
212
+ prStatus: input.pr_status,
213
+ workflowRunId: input.workflow_run_id,
214
+ workflowName: input.workflow_name,
215
+ workflowStatus: input.workflow_status,
216
+ })
217
+ const rawTeamDb = teamDb.getRawDb()
218
+ rawTeamDb.run('UPDATE memory_journal SET author = ? WHERE id = ?', [
219
+ author,
220
+ teamEntry.id,
221
+ ])
222
+ teamDb.flushSave()
223
+ sharedWithTeam = true
224
+ } catch {
225
+ // Team share failed — entry still saved to personal DB
226
+ }
227
+ }
228
+
229
+ return {
230
+ success: true,
231
+ entry,
232
+ ...(sharedWithTeam ? { sharedWithTeam: true, author } : {}),
233
+ }
234
+ } catch (err) {
235
+ return formatHandlerError(err)
236
+ }
237
+ },
238
+ },
239
+ {
240
+ name: 'get_entry_by_id',
241
+ title: 'Get Entry by ID',
242
+ description: 'Get a specific journal entry by ID with full details',
243
+ group: 'core',
244
+ inputSchema: GetEntryByIdSchema,
245
+ outputSchema: EntryByIdOutputSchema,
246
+ annotations: { readOnlyHint: true, idempotentHint: true },
247
+ handler: (params: unknown) => {
248
+ try {
249
+ const { entry_id, include_relationships } = GetEntryByIdSchema.parse(params)
250
+ const entry = db.getEntryById(entry_id)
251
+ if (!entry) {
252
+ return { success: false, error: `Entry ${String(entry_id)} not found` }
253
+ }
254
+ const { score: importance, breakdown: importanceBreakdown } =
255
+ db.calculateImportance(entry_id)
256
+ const result: Record<string, unknown> = {
257
+ entry,
258
+ importance,
259
+ importanceBreakdown,
260
+ }
261
+ if (include_relationships) {
262
+ result['relationships'] = db.getRelationships(entry_id)
263
+ }
264
+ return result
265
+ } catch (err) {
266
+ return formatHandlerError(err)
267
+ }
268
+ },
269
+ },
270
+ {
271
+ name: 'get_recent_entries',
272
+ title: 'Get Recent Entries',
273
+ description: 'Get recent journal entries',
274
+ group: 'core',
275
+ inputSchema: GetRecentEntriesSchema,
276
+ outputSchema: EntriesListOutputSchema,
277
+ annotations: { readOnlyHint: true, idempotentHint: true },
278
+ handler: (params: unknown) => {
279
+ try {
280
+ const { limit, is_personal } = GetRecentEntriesSchema.parse(params)
281
+ const entries = db.getRecentEntries(limit, is_personal)
282
+ return { entries, count: entries.length }
283
+ } catch (err) {
284
+ return formatHandlerError(err)
285
+ }
286
+ },
287
+ },
288
+ {
289
+ name: 'create_entry_minimal',
290
+ title: 'Create Entry (Minimal)',
291
+ description: 'Minimal entry creation without context or tags',
292
+ group: 'core',
293
+ inputSchema: CreateEntryMinimalSchema,
294
+ outputSchema: CreateEntryOutputSchema,
295
+ annotations: { readOnlyHint: false, idempotentHint: false },
296
+ handler: (params: unknown) => {
297
+ try {
298
+ const { content } = CreateEntryMinimalSchema.parse(params)
299
+ const entry = db.createEntry({ content })
300
+
301
+ // Auto-index to vector store for semantic search (fire-and-forget)
302
+ if (vectorManager) {
303
+ vectorManager.addEntry(entry.id, entry.content).catch(() => {
304
+ // Non-critical failure, entry already saved to DB
305
+ })
306
+ }
307
+
308
+ return { success: true, entry }
309
+ } catch (err) {
310
+ return formatHandlerError(err)
311
+ }
312
+ },
313
+ },
314
+ {
315
+ name: 'test_simple',
316
+ title: 'Test Simple',
317
+ description: 'Simple test tool that just returns a message',
318
+ group: 'core',
319
+ inputSchema: TestSimpleSchema,
320
+ outputSchema: TestSimpleOutputSchema,
321
+ annotations: { readOnlyHint: true, idempotentHint: true },
322
+ handler: (params: unknown) => {
323
+ try {
324
+ const { message } = TestSimpleSchema.parse(params)
325
+ return { message: `Test response: ${message}` }
326
+ } catch (err) {
327
+ return formatHandlerError(err)
328
+ }
329
+ },
330
+ },
331
+ {
332
+ name: 'list_tags',
333
+ title: 'List Tags',
334
+ description: 'List all available tags',
335
+ group: 'core',
336
+ inputSchema: z.object({}),
337
+ outputSchema: TagsListOutputSchema,
338
+ annotations: { readOnlyHint: true, idempotentHint: true },
339
+ handler: (_params: unknown) => {
340
+ try {
341
+ const rawTags = db.listTags()
342
+ const tags = rawTags.map((t) => ({ name: t.name, count: t.usageCount }))
343
+ return { tags, count: tags.length }
344
+ } catch (err) {
345
+ return formatHandlerError(err)
346
+ }
347
+ },
348
+ },
349
+ ]
350
+ }