memory-journal-mcp 4.4.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/codeql.yml +1 -6
- package/.github/workflows/docker-publish.yml +15 -49
- package/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/secrets-scanning.yml +4 -3
- package/.github/workflows/security-update.yml +3 -3
- package/CHANGELOG.md +213 -0
- package/CONTRIBUTING.md +132 -97
- package/DOCKER_README.md +184 -235
- package/Dockerfile +27 -24
- package/README.md +218 -190
- package/SECURITY.md +27 -35
- package/dist/cli.js +16 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +133 -73
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -2
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +7 -6
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +37 -24
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +319 -157
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/database/schema.d.ts +45 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +92 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +13 -2
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +1 -3
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/github.d.ts +12 -0
- package/dist/handlers/prompts/github.d.ts.map +1 -0
- package/dist/handlers/prompts/github.js +178 -0
- package/dist/handlers/prompts/github.js.map +1 -0
- package/dist/handlers/prompts/index.d.ts +23 -2
- package/dist/handlers/prompts/index.d.ts.map +1 -1
- package/dist/handlers/prompts/index.js +7 -432
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/prompts/workflow.d.ts +12 -0
- package/dist/handlers/prompts/workflow.d.ts.map +1 -0
- package/dist/handlers/prompts/workflow.js +277 -0
- package/dist/handlers/prompts/workflow.js.map +1 -0
- package/dist/handlers/resources/core.d.ts +11 -0
- package/dist/handlers/resources/core.d.ts.map +1 -0
- package/dist/handlers/resources/core.js +433 -0
- package/dist/handlers/resources/core.js.map +1 -0
- package/dist/handlers/resources/github.d.ts +11 -0
- package/dist/handlers/resources/github.d.ts.map +1 -0
- package/dist/handlers/resources/github.js +314 -0
- package/dist/handlers/resources/github.js.map +1 -0
- package/dist/handlers/resources/graph.d.ts +11 -0
- package/dist/handlers/resources/graph.d.ts.map +1 -0
- package/dist/handlers/resources/graph.js +204 -0
- package/dist/handlers/resources/graph.js.map +1 -0
- package/dist/handlers/resources/index.d.ts +5 -20
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +16 -1278
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/resources/shared.d.ts +60 -0
- package/dist/handlers/resources/shared.d.ts.map +1 -0
- package/dist/handlers/resources/shared.js +49 -0
- package/dist/handlers/resources/shared.js.map +1 -0
- package/dist/handlers/resources/team.d.ts +13 -0
- package/dist/handlers/resources/team.d.ts.map +1 -0
- package/dist/handlers/resources/team.js +119 -0
- package/dist/handlers/resources/team.js.map +1 -0
- package/dist/handlers/resources/templates.d.ts +13 -0
- package/dist/handlers/resources/templates.d.ts.map +1 -0
- package/dist/handlers/resources/templates.js +310 -0
- package/dist/handlers/resources/templates.js.map +1 -0
- package/dist/handlers/tools/admin.d.ts +8 -0
- package/dist/handlers/tools/admin.d.ts.map +1 -0
- package/dist/handlers/tools/admin.js +270 -0
- package/dist/handlers/tools/admin.js.map +1 -0
- package/dist/handlers/tools/analytics.d.ts +8 -0
- package/dist/handlers/tools/analytics.d.ts.map +1 -0
- package/dist/handlers/tools/analytics.js +256 -0
- package/dist/handlers/tools/analytics.js.map +1 -0
- package/dist/handlers/tools/backup.d.ts +8 -0
- package/dist/handlers/tools/backup.d.ts.map +1 -0
- package/dist/handlers/tools/backup.js +224 -0
- package/dist/handlers/tools/backup.js.map +1 -0
- package/dist/handlers/tools/core.d.ts +9 -0
- package/dist/handlers/tools/core.d.ts.map +1 -0
- package/dist/handlers/tools/core.js +326 -0
- package/dist/handlers/tools/core.js.map +1 -0
- package/dist/handlers/tools/export.d.ts +8 -0
- package/dist/handlers/tools/export.d.ts.map +1 -0
- package/dist/handlers/tools/export.js +89 -0
- package/dist/handlers/tools/export.js.map +1 -0
- package/dist/handlers/tools/github/helpers.d.ts +34 -0
- package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
- package/dist/handlers/tools/github/helpers.js +52 -0
- package/dist/handlers/tools/github/helpers.js.map +1 -0
- package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
- package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/insights-tools.js +104 -0
- package/dist/handlers/tools/github/insights-tools.js.map +1 -0
- package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
- package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/issue-tools.js +359 -0
- package/dist/handlers/tools/github/issue-tools.js.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.js +108 -0
- package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.js +302 -0
- package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.js +15 -0
- package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
- package/dist/handlers/tools/github/read-tools.d.ts +8 -0
- package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/read-tools.js +260 -0
- package/dist/handlers/tools/github/read-tools.js.map +1 -0
- package/dist/handlers/tools/github/schemas.d.ts +467 -0
- package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/github/schemas.js +335 -0
- package/dist/handlers/tools/github/schemas.js.map +1 -0
- package/dist/handlers/tools/github.d.ts +14 -0
- package/dist/handlers/tools/github.d.ts.map +1 -0
- package/dist/handlers/tools/github.js +28 -0
- package/dist/handlers/tools/github.js.map +1 -0
- package/dist/handlers/tools/index.d.ts +15 -20
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +117 -2909
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/handlers/tools/relationships.d.ts +8 -0
- package/dist/handlers/tools/relationships.d.ts.map +1 -0
- package/dist/handlers/tools/relationships.js +308 -0
- package/dist/handlers/tools/relationships.js.map +1 -0
- package/dist/handlers/tools/schemas.d.ts +108 -0
- package/dist/handlers/tools/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/schemas.js +122 -0
- package/dist/handlers/tools/schemas.js.map +1 -0
- package/dist/handlers/tools/search.d.ts +8 -0
- package/dist/handlers/tools/search.d.ts.map +1 -0
- package/dist/handlers/tools/search.js +282 -0
- package/dist/handlers/tools/search.js.map +1 -0
- package/dist/handlers/tools/team.d.ts +11 -0
- package/dist/handlers/tools/team.d.ts.map +1 -0
- package/dist/handlers/tools/team.js +239 -0
- package/dist/handlers/tools/team.js.map +1 -0
- package/dist/server/McpServer.d.ts +4 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +48 -297
- package/dist/server/McpServer.js.map +1 -1
- package/dist/server/Scheduler.d.ts +91 -0
- package/dist/server/Scheduler.d.ts.map +1 -0
- package/dist/server/Scheduler.js +201 -0
- package/dist/server/Scheduler.js.map +1 -0
- package/dist/transports/http.d.ts +66 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +519 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/types/entities.d.ts +101 -0
- package/dist/types/entities.d.ts.map +1 -0
- package/dist/types/entities.js +5 -0
- package/dist/types/entities.js.map +1 -0
- package/dist/types/filtering.d.ts +34 -0
- package/dist/types/filtering.d.ts.map +1 -0
- package/dist/types/filtering.js +5 -0
- package/dist/types/filtering.js.map +1 -0
- package/dist/types/github.d.ts +166 -0
- package/dist/types/github.d.ts.map +1 -0
- package/dist/types/github.js +5 -0
- package/dist/types/github.js.map +1 -0
- package/dist/types/index.d.ts +35 -292
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-helpers.d.ts +37 -0
- package/dist/utils/error-helpers.d.ts.map +1 -0
- package/dist/utils/error-helpers.js +47 -0
- package/dist/utils/error-helpers.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +0 -21
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +0 -47
- package/dist/utils/security-utils.js.map +1 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +9 -32
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +11 -2
- package/hooks/README.md +107 -0
- package/hooks/cursor/hooks.json +10 -0
- package/hooks/cursor/memory-journal.mdc +22 -0
- package/hooks/cursor/session-end.sh +19 -0
- package/hooks/kilo-code/session-end-mode.json +11 -0
- package/hooks/kiro/session-end.md +13 -0
- package/mcp-config-example.json +1 -0
- package/package.json +11 -9
- package/playwright.config.ts +29 -0
- package/releases/v4.5.0.md +116 -0
- package/releases/v5.0.0.md +105 -0
- package/scripts/generate-server-instructions.ts +176 -0
- package/scripts/server-instructions-function-body.ts +77 -0
- package/server.json +3 -3
- package/src/cli.ts +45 -1
- package/src/constants/ServerInstructions.ts +133 -73
- package/src/constants/icons.ts +8 -7
- package/src/constants/server-instructions.md +268 -0
- package/src/database/SqliteAdapter.ts +358 -192
- package/src/database/schema.ts +125 -0
- package/src/filtering/ToolFilter.ts +13 -2
- package/src/github/GitHubIntegration.ts +1 -3
- package/src/handlers/prompts/github.ts +209 -0
- package/src/handlers/prompts/index.ts +10 -499
- package/src/handlers/prompts/workflow.ts +314 -0
- package/src/handlers/resources/core.ts +528 -0
- package/src/handlers/resources/github.ts +358 -0
- package/src/handlers/resources/graph.ts +254 -0
- package/src/handlers/resources/index.ts +23 -1570
- package/src/handlers/resources/shared.ts +103 -0
- package/src/handlers/resources/team.ts +133 -0
- package/src/handlers/resources/templates.ts +374 -0
- package/src/handlers/tools/admin.ts +285 -0
- package/src/handlers/tools/analytics.ts +301 -0
- package/src/handlers/tools/backup.ts +242 -0
- package/src/handlers/tools/core.ts +350 -0
- package/src/handlers/tools/export.ts +115 -0
- package/src/handlers/tools/github/helpers.ts +86 -0
- package/src/handlers/tools/github/insights-tools.ts +119 -0
- package/src/handlers/tools/github/issue-tools.ts +439 -0
- package/src/handlers/tools/github/kanban-tools.ts +134 -0
- package/src/handlers/tools/github/milestone-tools.ts +392 -0
- package/src/handlers/tools/github/mutation-tools.ts +17 -0
- package/src/handlers/tools/github/read-tools.ts +328 -0
- package/src/handlers/tools/github/schemas.ts +369 -0
- package/src/handlers/tools/github.ts +36 -0
- package/src/handlers/tools/index.ts +144 -3325
- package/src/handlers/tools/relationships.ts +358 -0
- package/src/handlers/tools/schemas.ts +132 -0
- package/src/handlers/tools/search.ts +343 -0
- package/src/handlers/tools/team.ts +273 -0
- package/src/server/McpServer.ts +63 -358
- package/src/server/Scheduler.ts +278 -0
- package/src/transports/http.ts +635 -0
- package/src/types/entities.ts +145 -0
- package/src/types/filtering.ts +54 -0
- package/src/types/github.ts +180 -0
- package/src/types/index.ts +67 -375
- package/src/utils/error-helpers.ts +52 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/src/vector/VectorSearchManager.ts +9 -33
- package/tests/constants/icons.test.ts +1 -2
- package/tests/constants/server-instructions.test.ts +30 -4
- package/tests/database/sqlite-adapter.test.ts +91 -7
- package/tests/e2e/auth.spec.ts +154 -0
- package/tests/e2e/health.spec.ts +63 -0
- package/tests/e2e/protocols.spec.ts +134 -0
- package/tests/e2e/resources.spec.ts +103 -0
- package/tests/e2e/scheduler.spec.ts +79 -0
- package/tests/e2e/security.spec.ts +91 -0
- package/tests/e2e/sessions.spec.ts +95 -0
- package/tests/e2e/stateless.spec.ts +121 -0
- package/tests/e2e/tools.spec.ts +111 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/error-path-coverage.test.ts +324 -0
- package/tests/handlers/github-resource-handlers.test.ts +453 -0
- package/tests/handlers/github-tool-handlers.test.ts +899 -0
- package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handler-coverage.test.ts +181 -0
- package/tests/handlers/resource-handlers.test.ts +33 -9
- package/tests/handlers/search-tool-handlers.test.ts +272 -0
- package/tests/handlers/targeted-gap-closure.test.ts +387 -0
- package/tests/handlers/team-resource-handlers.test.ts +156 -0
- package/tests/handlers/team-tool-handlers.test.ts +301 -0
- package/tests/handlers/tool-handler-coverage.test.ts +469 -0
- package/tests/handlers/tool-handlers.test.ts +2 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +503 -8
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/transports/http-transport.test.ts +620 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/vitest.config.ts +4 -1
- package/.memory-journal-team.db +0 -0
- package/.vscode/settings.json +0 -84
package/src/server/McpServer.ts
CHANGED
|
@@ -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
|
|
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
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|