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
@@ -7,12 +7,6 @@
7
7
  import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
8
8
  import type { Variables } from '@modelcontextprotocol/sdk/shared/uriTemplate.js'
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
10
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
11
- import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'
12
- import { randomUUID } from 'node:crypto'
13
- import type { IncomingMessage, ServerResponse } from 'node:http'
14
- import express from 'express'
15
- import type { Express, Request, Response } from 'express'
16
10
  import { z } from 'zod'
17
11
 
18
12
  import { SqliteAdapter } from '../database/SqliteAdapter.js'
@@ -29,37 +23,45 @@ import { getTools, callTool } from '../handlers/tools/index.js'
29
23
  import { getResources, readResource } from '../handlers/resources/index.js'
30
24
  import { getPrompts, getPrompt } from '../handlers/prompts/index.js'
31
25
  import { generateInstructions } from '../constants/ServerInstructions.js'
26
+ import { Scheduler, type SchedulerOptions } from './Scheduler.js'
27
+ import { HttpTransport } from '../transports/http.js'
32
28
  import pkg from '../../package.json' with { type: 'json' }
33
29
 
34
- /** Session timeout for stateful HTTP mode (30 minutes) */
35
- const SESSION_TIMEOUT_MS = 30 * 60 * 1000
36
-
37
- /** Session timeout sweep interval (5 minutes) */
38
- const SESSION_SWEEP_INTERVAL_MS = 5 * 60 * 1000
39
-
40
30
  export interface ServerOptions {
41
31
  transport: 'stdio' | 'http'
42
32
  port?: number
43
33
  host?: string
44
34
  dbPath: string
35
+ teamDbPath?: string
45
36
  toolFilter?: string
46
37
  defaultProjectNumber?: number
47
38
  autoRebuildIndex?: boolean
48
39
  statelessHttp?: boolean
49
40
  corsOrigin?: string
41
+ authToken?: string
42
+ scheduler?: SchedulerOptions
50
43
  }
51
44
 
52
45
  /**
53
46
  * Create and start the MCP server
54
47
  */
55
48
  export async function createServer(options: ServerOptions): Promise<void> {
56
- const { transport, dbPath, toolFilter, defaultProjectNumber } = options
49
+ const { transport, dbPath, teamDbPath, toolFilter, defaultProjectNumber } = options
57
50
 
58
51
  // Initialize database (async for sql.js)
59
52
  const db = new SqliteAdapter(dbPath)
60
53
  await db.initialize()
61
54
  logger.info('Database initialized', { module: 'McpServer', dbPath })
62
55
 
56
+ // Initialize team database if configured
57
+ let teamDb: SqliteAdapter | undefined
58
+ if (teamDbPath) {
59
+ teamDb = new SqliteAdapter(teamDbPath)
60
+ await teamDb.initialize()
61
+ teamDb.applyTeamSchema()
62
+ logger.info('Team database initialized', { module: 'McpServer', teamDbPath })
63
+ }
64
+
63
65
  // Initialize vector search manager (lazy loading - model loads on first use)
64
66
  const vectorManager = new VectorSearchManager(dbPath)
65
67
  logger.info('Vector search manager created (lazy initialization)', { module: 'McpServer' })
@@ -116,7 +118,7 @@ export async function createServer(options: ServerOptions): Promise<void> {
116
118
  : undefined
117
119
 
118
120
  // Get all tools once (unfiltered) for both instruction generation and registration
119
- const allTools = getTools(db, null, vectorManager, github, { defaultProjectNumber })
121
+ const allTools = getTools(db, null, vectorManager, github, { defaultProjectNumber }, teamDb)
120
122
  const allToolNames = new Set(allTools.map((t) => (t as { name: string }).name))
121
123
 
122
124
  // Generate dynamic instructions based on enabled tools, resources, prompts, and latest entry
@@ -149,7 +151,7 @@ export async function createServer(options: ServerOptions): Promise<void> {
149
151
 
150
152
  // Apply filter to get the set of tools to register
151
153
  const tools = filterConfig
152
- ? getTools(db, filterConfig, vectorManager, github, { defaultProjectNumber })
154
+ ? getTools(db, filterConfig, vectorManager, github, { defaultProjectNumber }, teamDb)
153
155
  : allTools
154
156
  for (const tool of tools) {
155
157
  const toolDef = tool as {
@@ -212,7 +214,8 @@ export async function createServer(options: ServerOptions): Promise<void> {
212
214
  vectorManager,
213
215
  github,
214
216
  { defaultProjectNumber },
215
- progressContext
217
+ progressContext,
218
+ teamDb
216
219
  )
217
220
 
218
221
  // MCP 2025-11-25: If tool has outputSchema, return both:
@@ -290,7 +293,9 @@ export async function createServer(options: ServerOptions): Promise<void> {
290
293
  db,
291
294
  vectorManager,
292
295
  filterConfig,
293
- github
296
+ github,
297
+ scheduler,
298
+ teamDb
294
299
  )
295
300
  const dataStr =
296
301
  typeof result.data === 'string'
@@ -324,7 +329,9 @@ export async function createServer(options: ServerOptions): Promise<void> {
324
329
  db,
325
330
  vectorManager,
326
331
  filterConfig,
327
- github
332
+ github,
333
+ scheduler,
334
+ teamDb
328
335
  )
329
336
  const dataStr =
330
337
  typeof result.data === 'string'
@@ -388,6 +395,25 @@ export async function createServer(options: ServerOptions): Promise<void> {
388
395
  )
389
396
  }
390
397
 
398
+ // Initialize scheduler (HTTP/SSE only)
399
+ let scheduler: Scheduler | null = null
400
+ if (options.scheduler) {
401
+ const hasAnyJob =
402
+ options.scheduler.backupIntervalMinutes > 0 ||
403
+ options.scheduler.vacuumIntervalMinutes > 0 ||
404
+ options.scheduler.rebuildIndexIntervalMinutes > 0
405
+
406
+ if (hasAnyJob && transport === 'stdio') {
407
+ logger.warning(
408
+ 'Scheduler options ignored for stdio transport (session is ephemeral). ' +
409
+ 'Use HTTP/SSE transport for automated scheduling.',
410
+ { module: 'Scheduler' }
411
+ )
412
+ } else if (hasAnyJob) {
413
+ scheduler = new Scheduler(options.scheduler, db, vectorManager)
414
+ }
415
+ }
416
+
391
417
  // Start server based on transport
392
418
  if (transport === 'stdio') {
393
419
  const stdioTransport = new StdioServerTransport()
@@ -398,356 +424,35 @@ export async function createServer(options: ServerOptions): Promise<void> {
398
424
  process.on('SIGINT', () => {
399
425
  logger.info('Shutting down...', { module: 'McpServer' })
400
426
  db.close()
427
+ teamDb?.close()
401
428
  process.exit(0)
402
429
  })
403
430
  } else {
404
- // HTTP transport with SSE support
431
+ // HTTP transport
405
432
  const port = options.port ?? 3000
406
433
  const host = options.host ?? 'localhost'
407
434
  const corsOrigin = options.corsOrigin ?? process.env['MCP_CORS_ORIGIN'] ?? '*'
408
- const app: Express = express()
409
-
410
- // Security headers middleware
411
- app.use((_req: Request, res: Response, next: () => void) => {
412
- res.setHeader('X-Content-Type-Options', 'nosniff')
413
- res.setHeader('X-Frame-Options', 'DENY')
414
- next()
435
+ const authToken = options.authToken ?? process.env['MCP_AUTH_TOKEN'] ?? undefined
436
+
437
+ const httpTransport = new HttpTransport({
438
+ port,
439
+ host,
440
+ corsOrigin,
441
+ stateless: options.statelessHttp === true,
442
+ authToken,
415
443
  })
416
444
 
417
- // CORS middleware for browser-based clients (e.g., MCP Inspector)
418
- // Origin is configurable via --cors-origin flag or MCP_CORS_ORIGIN env var
419
- app.use((req: Request, res: Response, next: () => void) => {
420
- res.setHeader('Access-Control-Allow-Origin', corsOrigin)
421
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
422
- res.setHeader(
423
- 'Access-Control-Allow-Headers',
424
- 'Content-Type, Accept, mcp-session-id, Last-Event-ID, mcp-protocol-version'
425
- )
426
- res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id')
427
-
428
- // Handle OPTIONS preflight requests
429
- if (req.method === 'OPTIONS') {
430
- res.status(204).end()
431
- return
432
- }
433
-
434
- next()
435
- })
445
+ await httpTransport.start(server, scheduler)
436
446
 
437
- // JSON body parser with size limit to prevent memory exhaustion (DoS)
438
- app.use(express.json({ limit: '1mb' }))
439
-
440
- // Explicit OPTIONS handler for /mcp - MUST be before other /mcp routes
441
- // Using app.all to intercept before Express 5's auto-OPTIONS
442
- app.all('/mcp', (req: Request, res: Response, next: () => void) => {
443
- // CORS headers are already set by the middleware above.
444
- // For OPTIONS, respond immediately.
445
- if (req.method === 'OPTIONS') {
446
- res.status(204).end()
447
- return
448
- }
449
-
450
- // For other methods, continue to next handler
451
- next()
447
+ // Handle shutdown
448
+ process.on('SIGINT', () => {
449
+ void (async () => {
450
+ await httpTransport.stop(scheduler)
451
+ db.close()
452
+ teamDb?.close()
453
+ process.exit(0)
454
+ })()
452
455
  })
453
-
454
- if (options.statelessHttp) {
455
- // === STATELESS MODE ===
456
- // Single transport, no session management - ideal for serverless deployments
457
- const statelessTransport = new StreamableHTTPServerTransport({
458
- sessionIdGenerator: undefined,
459
- enableJsonResponse: true,
460
- })
461
-
462
- await server.connect(statelessTransport)
463
- logger.info('Stateless transport connected', { module: 'McpServer' })
464
-
465
- // POST /mcp - All requests go to the same transport (no session validation)
466
- app.post('/mcp', (req: Request, res: Response): void => {
467
- void statelessTransport.handleRequest(
468
- req as unknown as IncomingMessage,
469
- res as unknown as ServerResponse,
470
- req.body as unknown
471
- )
472
- })
473
-
474
- // GET /mcp - SSE not available in stateless mode
475
- app.get('/mcp', (_req: Request, res: Response): void => {
476
- res.status(405).json({
477
- jsonrpc: '2.0',
478
- error: {
479
- code: -32000,
480
- message: 'SSE streaming not available in stateless mode',
481
- },
482
- id: null,
483
- })
484
- })
485
-
486
- // DELETE /mcp - No-op in stateless mode (no sessions to terminate)
487
- app.delete('/mcp', (_req: Request, res: Response): void => {
488
- res.status(204).end()
489
- })
490
-
491
- // Start HTTP server
492
- const httpServer = app.listen(port, host, () => {
493
- logger.info('MCP server started on HTTP (stateless)', {
494
- module: 'McpServer',
495
- port,
496
- host,
497
- endpoint: `http://${host}:${port}/mcp`,
498
- })
499
- })
500
-
501
- httpServer.on('close', () => {
502
- logger.info('HTTP server closed', { module: 'McpServer' })
503
- })
504
-
505
- // Handle shutdown
506
- process.on('SIGINT', () => {
507
- logger.info('Shutting down HTTP server...', { module: 'McpServer' })
508
- void (async () => {
509
- try {
510
- await statelessTransport.close()
511
- } catch (error) {
512
- logger.error('Error closing transport', {
513
- module: 'McpServer',
514
- error: error instanceof Error ? error.message : String(error),
515
- })
516
- }
517
- httpServer.close()
518
- db.close()
519
- logger.info('Shutdown complete', { module: 'McpServer' })
520
- process.exit(0)
521
- })()
522
- })
523
-
524
- // Keep process alive
525
- setInterval(
526
- () => {
527
- // Heartbeat - keeps event loop active
528
- },
529
- 1000 * 60 * 60
530
- )
531
- } else {
532
- // === STATEFUL MODE ===
533
- // Session-based transport with SSE support for notifications
534
-
535
- // Session transport storage with last-activity timestamps
536
- const transports = new Map<string, StreamableHTTPServerTransport>()
537
- const sessionLastActivity = new Map<string, number>()
538
-
539
- /** Update the last-activity timestamp for a session */
540
- const touchSession = (sid: string): void => {
541
- sessionLastActivity.set(sid, Date.now())
542
- }
543
-
544
- /** Sweep expired sessions (called periodically) */
545
- const sweepExpiredSessions = (): void => {
546
- const now = Date.now()
547
- for (const [sid, lastActivity] of sessionLastActivity) {
548
- if (now - lastActivity > SESSION_TIMEOUT_MS && transports.has(sid)) {
549
- logger.info('Expiring idle HTTP session', {
550
- module: 'McpServer',
551
- sessionId: sid,
552
- idleMinutes: Math.round((now - lastActivity) / 60_000),
553
- })
554
- const t = transports.get(sid)
555
- if (t) {
556
- void t.close()
557
- }
558
- transports.delete(sid)
559
- sessionLastActivity.delete(sid)
560
- }
561
- }
562
- }
563
-
564
- // Start session timeout sweep (runs every 5 minutes)
565
- const sessionSweepTimer = setInterval(sweepExpiredSessions, SESSION_SWEEP_INTERVAL_MS)
566
-
567
- // POST /mcp - Handle JSON-RPC requests
568
- app.post('/mcp', (req: Request, res: Response): void => {
569
- const sessionId = req.headers['mcp-session-id'] as string | undefined
570
-
571
- void (async () => {
572
- try {
573
- let httpTransport: StreamableHTTPServerTransport | undefined
574
-
575
- if (sessionId && transports.has(sessionId)) {
576
- // Reuse existing transport and refresh session activity
577
- touchSession(sessionId)
578
- httpTransport = transports.get(sessionId)
579
- } else if (sessionId === undefined && isInitializeRequest(req.body)) {
580
- // New initialization request - create transport
581
- const newTransport = new StreamableHTTPServerTransport({
582
- sessionIdGenerator: () => randomUUID(),
583
- onsessioninitialized: (sid: string) => {
584
- logger.info('HTTP session initialized', {
585
- module: 'McpServer',
586
- sessionId: sid,
587
- })
588
- transports.set(sid, newTransport)
589
- touchSession(sid)
590
- },
591
- })
592
-
593
- // Clean up on transport close
594
- newTransport.onclose = () => {
595
- const sid = newTransport.sessionId
596
- if (sid !== undefined && transports.has(sid)) {
597
- logger.info('HTTP transport closed', {
598
- module: 'McpServer',
599
- sessionId: sid,
600
- })
601
- transports.delete(sid)
602
- sessionLastActivity.delete(sid)
603
- }
604
- }
605
-
606
- // Connect transport to server before handling request
607
- await server.connect(newTransport)
608
- await newTransport.handleRequest(
609
- req as unknown as IncomingMessage,
610
- res as unknown as ServerResponse,
611
- req.body as unknown
612
- )
613
- return
614
- } else {
615
- // Invalid request - no session ID or not initialization
616
- res.status(400).json({
617
- jsonrpc: '2.0',
618
- error: {
619
- code: -32000,
620
- message: 'Bad Request: No valid session ID provided',
621
- },
622
- id: null,
623
- })
624
- return
625
- }
626
-
627
- // Handle request with existing transport
628
- if (httpTransport !== undefined) {
629
- await httpTransport.handleRequest(
630
- req as unknown as IncomingMessage,
631
- res as unknown as ServerResponse,
632
- req.body as unknown
633
- )
634
- }
635
- } catch (error) {
636
- logger.error('Error handling MCP request', {
637
- module: 'McpServer',
638
- error: error instanceof Error ? error.message : String(error),
639
- })
640
- if (!res.headersSent) {
641
- res.status(500).json({
642
- jsonrpc: '2.0',
643
- error: { code: -32603, message: 'Internal server error' },
644
- id: null,
645
- })
646
- }
647
- }
648
- })()
649
- })
650
-
651
- // GET /mcp - SSE stream for server-to-client notifications
652
- app.get('/mcp', (req: Request, res: Response): void => {
653
- const sessionId = req.headers['mcp-session-id'] as string | undefined
654
-
655
- if (sessionId === undefined || !transports.has(sessionId)) {
656
- res.status(400).send('Invalid or missing session ID')
657
- return
658
- }
659
-
660
- // Refresh session activity on SSE reconnect
661
- touchSession(sessionId)
662
-
663
- const lastEventId = req.headers['last-event-id']
664
- if (lastEventId !== undefined) {
665
- logger.debug('Client reconnecting with Last-Event-ID', {
666
- module: 'McpServer',
667
- sessionId,
668
- lastEventId,
669
- })
670
- }
671
-
672
- const httpTransport = transports.get(sessionId)
673
- if (httpTransport !== undefined) {
674
- void httpTransport.handleRequest(
675
- req as unknown as IncomingMessage,
676
- res as unknown as ServerResponse
677
- )
678
- }
679
- })
680
-
681
- // DELETE /mcp - Session termination
682
- app.delete('/mcp', (req: Request, res: Response): void => {
683
- const sessionId = req.headers['mcp-session-id'] as string | undefined
684
-
685
- if (sessionId === undefined || !transports.has(sessionId)) {
686
- res.status(400).send('Invalid or missing session ID')
687
- return
688
- }
689
-
690
- logger.info('Session termination requested', {
691
- module: 'McpServer',
692
- sessionId,
693
- })
694
-
695
- const httpTransport = transports.get(sessionId)
696
- if (httpTransport !== undefined) {
697
- void httpTransport.handleRequest(
698
- req as unknown as IncomingMessage,
699
- res as unknown as ServerResponse
700
- )
701
- }
702
- })
703
-
704
- // Start HTTP server
705
- const httpServer = app.listen(port, host, () => {
706
- logger.info('MCP server started on HTTP (stateful)', {
707
- module: 'McpServer',
708
- port,
709
- host,
710
- endpoint: `http://${host}:${port}/mcp`,
711
- })
712
- })
713
-
714
- // Keep process alive - httpServer keeps the event loop active
715
- // but we also ensure it doesn't close prematurely
716
- httpServer.on('close', () => {
717
- logger.info('HTTP server closed', { module: 'McpServer' })
718
- })
719
-
720
- // Handle shutdown for HTTP - must be registered before blocking await
721
- process.on('SIGINT', () => {
722
- logger.info('Shutting down HTTP server...', { module: 'McpServer' })
723
-
724
- void (async () => {
725
- // Close all active transports
726
- for (const [sessionId, httpTransport] of transports) {
727
- try {
728
- logger.debug('Closing transport', { module: 'McpServer', sessionId })
729
- await httpTransport.close()
730
- } catch (error) {
731
- logger.error('Error closing transport', {
732
- module: 'McpServer',
733
- sessionId,
734
- error: error instanceof Error ? error.message : String(error),
735
- })
736
- }
737
- }
738
- transports.clear()
739
- sessionLastActivity.clear()
740
- clearInterval(sessionSweepTimer)
741
-
742
- httpServer.close()
743
- db.close()
744
- logger.info('Shutdown complete', { module: 'McpServer' })
745
- process.exit(0)
746
- })()
747
- })
748
-
749
- // sessionSweepTimer keeps the event loop active (no additional heartbeat needed)
750
- }
751
456
  }
752
457
  }
753
458