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
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-deprecated -- Intentional: SSEServerTransport provides backward compatibility for MCP 2024-11-05 clients */
|
|
2
|
+
/**
|
|
3
|
+
* Memory Journal MCP Server - HTTP Transport
|
|
4
|
+
*
|
|
5
|
+
* Dual-protocol HTTP transport:
|
|
6
|
+
* - `/mcp` — Streamable HTTP transport (MCP 2025-03-26)
|
|
7
|
+
* - `/sse` + `/messages` — Legacy SSE transport (MCP 2024-11-05)
|
|
8
|
+
*
|
|
9
|
+
* Modes:
|
|
10
|
+
* - Stateful (default): Multi-session management with SSE streaming
|
|
11
|
+
* - Stateless (opt-in): Lightweight serverless-compatible mode
|
|
12
|
+
*
|
|
13
|
+
* Security: headers, CORS, rate limiting, body size enforcement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
|
|
17
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
|
|
18
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'
|
|
19
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
20
|
+
import { randomUUID, timingSafeEqual } from 'node:crypto'
|
|
21
|
+
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
22
|
+
import express from 'express'
|
|
23
|
+
import type { Express, Request, Response } from 'express'
|
|
24
|
+
import rateLimit from 'express-rate-limit'
|
|
25
|
+
import { logger } from '../utils/logger.js'
|
|
26
|
+
import type { Scheduler } from '../server/Scheduler.js'
|
|
27
|
+
import pkg from '../../package.json' with { type: 'json' }
|
|
28
|
+
|
|
29
|
+
/** Session timeout for stateful HTTP mode (30 minutes) */
|
|
30
|
+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000
|
|
31
|
+
|
|
32
|
+
/** Session timeout sweep interval (5 minutes) */
|
|
33
|
+
const SESSION_SWEEP_INTERVAL_MS = 5 * 60 * 1000
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* HTTP transport configuration
|
|
37
|
+
*/
|
|
38
|
+
export interface HttpTransportConfig {
|
|
39
|
+
port: number
|
|
40
|
+
host: string
|
|
41
|
+
corsOrigin: string
|
|
42
|
+
stateless: boolean
|
|
43
|
+
authToken?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* HTTP Transport for Memory Journal MCP Server
|
|
48
|
+
*
|
|
49
|
+
* Supports two transport protocols simultaneously:
|
|
50
|
+
* 1. Streamable HTTP (MCP 2025-03-26) via `/mcp` — preferred for modern clients
|
|
51
|
+
* 2. Legacy SSE (MCP 2024-11-05) via `/sse` + `/messages` — backward compatibility
|
|
52
|
+
*/
|
|
53
|
+
export class HttpTransport {
|
|
54
|
+
private readonly app: Express
|
|
55
|
+
private readonly config: HttpTransportConfig
|
|
56
|
+
private readonly transports = new Map<string, StreamableHTTPServerTransport>()
|
|
57
|
+
private readonly sseTransports = new Map<string, SSEServerTransport>()
|
|
58
|
+
private readonly sessionLastActivity = new Map<string, number>()
|
|
59
|
+
private httpServer: ReturnType<Express['listen']> | null = null
|
|
60
|
+
private sessionSweepTimer: ReturnType<typeof setInterval> | null = null
|
|
61
|
+
|
|
62
|
+
constructor(config: HttpTransportConfig) {
|
|
63
|
+
this.config = config
|
|
64
|
+
this.app = express()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initialize and start the HTTP transport
|
|
69
|
+
*/
|
|
70
|
+
async start(server: McpServer, scheduler: Scheduler | null): Promise<void> {
|
|
71
|
+
const { port, host, corsOrigin, authToken } = this.config
|
|
72
|
+
|
|
73
|
+
if (corsOrigin === '*') {
|
|
74
|
+
logger.warning(
|
|
75
|
+
'CORS origin is set to "*" (all origins). ' +
|
|
76
|
+
'Set --cors-origin or MCP_CORS_ORIGIN for production deployments.',
|
|
77
|
+
{ module: 'HTTP' }
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!authToken) {
|
|
82
|
+
logger.warning(
|
|
83
|
+
'No authentication configured for HTTP transport. ' +
|
|
84
|
+
'Set --auth-token or MCP_AUTH_TOKEN for production deployments.',
|
|
85
|
+
{ module: 'HTTP' }
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Security headers middleware
|
|
90
|
+
this.app.use((req: Request, res: Response, next: () => void) => {
|
|
91
|
+
res.setHeader('X-Content-Type-Options', 'nosniff')
|
|
92
|
+
res.setHeader('X-Frame-Options', 'DENY')
|
|
93
|
+
res.setHeader('Content-Security-Policy', "default-src 'none'; frame-ancestors 'none'")
|
|
94
|
+
res.setHeader('Cache-Control', 'no-store')
|
|
95
|
+
res.setHeader('Referrer-Policy', 'no-referrer')
|
|
96
|
+
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()')
|
|
97
|
+
// HSTS — only emit when behind a TLS-terminating reverse proxy
|
|
98
|
+
if (req.headers?.['x-forwarded-proto'] === 'https') {
|
|
99
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
|
|
100
|
+
}
|
|
101
|
+
next()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// CORS middleware
|
|
105
|
+
this.app.use((req: Request, res: Response, next: () => void) => {
|
|
106
|
+
res.setHeader('Access-Control-Allow-Origin', corsOrigin)
|
|
107
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
|
|
108
|
+
res.setHeader(
|
|
109
|
+
'Access-Control-Allow-Headers',
|
|
110
|
+
'Content-Type, Accept, Authorization, mcp-session-id, Last-Event-ID, mcp-protocol-version'
|
|
111
|
+
)
|
|
112
|
+
res.setHeader('Access-Control-Expose-Headers', 'mcp-session-id')
|
|
113
|
+
|
|
114
|
+
if (req.method === 'OPTIONS') {
|
|
115
|
+
res.status(204).end()
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
next()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// JSON body parser with size limit (DoS prevention)
|
|
123
|
+
this.app.use(express.json({ limit: '1mb' }))
|
|
124
|
+
|
|
125
|
+
// Rate limiting (100 requests/minute per IP)
|
|
126
|
+
const limiter = rateLimit({
|
|
127
|
+
windowMs: 60 * 1000,
|
|
128
|
+
limit: 100,
|
|
129
|
+
standardHeaders: 'draft-8',
|
|
130
|
+
legacyHeaders: false,
|
|
131
|
+
message: { error: 'Too many requests, please try again later' },
|
|
132
|
+
})
|
|
133
|
+
this.app.use(limiter)
|
|
134
|
+
logger.info('Rate limiting enabled: 100 requests/minute per IP', {
|
|
135
|
+
module: 'HTTP',
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// Bearer token authentication (when configured)
|
|
139
|
+
if (authToken) {
|
|
140
|
+
this.app.use((req: Request, res: Response, next: () => void) => {
|
|
141
|
+
if (req.path === '/health') {
|
|
142
|
+
next()
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const header = req.headers.authorization
|
|
147
|
+
const expected = Buffer.from(`Bearer ${authToken}`)
|
|
148
|
+
const received = Buffer.from(header ?? '')
|
|
149
|
+
if (expected.length !== received.length || !timingSafeEqual(expected, received)) {
|
|
150
|
+
res.status(401).json({ error: 'Unauthorized' })
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
next()
|
|
154
|
+
})
|
|
155
|
+
logger.info('Bearer token authentication enabled', { module: 'HTTP' })
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Health check endpoint (before /mcp routes)
|
|
159
|
+
this.app.get('/health', (_req: Request, res: Response): void => {
|
|
160
|
+
res.status(200).json({
|
|
161
|
+
status: 'healthy',
|
|
162
|
+
timestamp: new Date().toISOString(),
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// Root info endpoint
|
|
167
|
+
this.app.get('/', (_req: Request, res: Response): void => {
|
|
168
|
+
res.status(200).json({
|
|
169
|
+
name: 'memory-journal-mcp',
|
|
170
|
+
version: pkg.version,
|
|
171
|
+
description: 'Project context management for AI-assisted development',
|
|
172
|
+
endpoints: {
|
|
173
|
+
'POST /mcp': 'JSON-RPC requests (Streamable HTTP, MCP 2025-03-26)',
|
|
174
|
+
'GET /mcp': 'SSE stream for server-to-client notifications',
|
|
175
|
+
'DELETE /mcp': 'Session termination',
|
|
176
|
+
'GET /sse': 'Legacy SSE connection (MCP 2024-11-05)',
|
|
177
|
+
'POST /messages': 'Legacy SSE message endpoint',
|
|
178
|
+
'GET /health': 'Health check',
|
|
179
|
+
},
|
|
180
|
+
documentation: 'https://github.com/neverinfamous/memory-journal-mcp',
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// OPTIONS handler for /mcp — MUST be before other /mcp routes
|
|
185
|
+
this.app.all('/mcp', (req: Request, res: Response, next: () => void) => {
|
|
186
|
+
if (req.method === 'OPTIONS') {
|
|
187
|
+
res.status(204).end()
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
next()
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
if (this.config.stateless) {
|
|
194
|
+
await this.setupStateless(server)
|
|
195
|
+
} else {
|
|
196
|
+
this.setupStateful(server)
|
|
197
|
+
this.setupLegacySSE(server)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 404 handler — must be after all routes
|
|
201
|
+
this.app.use((_req: Request, res: Response): void => {
|
|
202
|
+
res.status(404).json({ error: 'Not found' })
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// Start HTTP server
|
|
206
|
+
this.httpServer = this.app.listen(port, host, () => {
|
|
207
|
+
logger.info(
|
|
208
|
+
`MCP server started on HTTP (${this.config.stateless ? 'stateless' : 'stateful'})`,
|
|
209
|
+
{
|
|
210
|
+
module: 'HTTP',
|
|
211
|
+
port,
|
|
212
|
+
host,
|
|
213
|
+
endpoint: `http://${host}:${port}/mcp`,
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// Start scheduler after HTTP server is listening
|
|
219
|
+
scheduler?.start()
|
|
220
|
+
|
|
221
|
+
this.httpServer.on('close', () => {
|
|
222
|
+
logger.info('HTTP server closed', { module: 'HTTP' })
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Graceful shutdown
|
|
228
|
+
*/
|
|
229
|
+
async stop(scheduler: Scheduler | null): Promise<void> {
|
|
230
|
+
logger.info('Shutting down HTTP server...', { module: 'HTTP' })
|
|
231
|
+
|
|
232
|
+
scheduler?.stop()
|
|
233
|
+
|
|
234
|
+
// Close all Streamable HTTP transports
|
|
235
|
+
for (const [sessionId, transport] of this.transports) {
|
|
236
|
+
try {
|
|
237
|
+
logger.debug('Closing Streamable HTTP transport', {
|
|
238
|
+
module: 'HTTP',
|
|
239
|
+
sessionId,
|
|
240
|
+
})
|
|
241
|
+
await transport.close()
|
|
242
|
+
} catch (error) {
|
|
243
|
+
logger.error('Error closing transport', {
|
|
244
|
+
module: 'HTTP',
|
|
245
|
+
sessionId,
|
|
246
|
+
error: error instanceof Error ? error.message : String(error),
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
this.transports.clear()
|
|
251
|
+
|
|
252
|
+
// Close all Legacy SSE transports
|
|
253
|
+
for (const [sessionId, transport] of this.sseTransports) {
|
|
254
|
+
try {
|
|
255
|
+
logger.debug('Closing Legacy SSE transport', {
|
|
256
|
+
module: 'HTTP',
|
|
257
|
+
sessionId,
|
|
258
|
+
})
|
|
259
|
+
await transport.close()
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logger.error('Error closing SSE transport', {
|
|
262
|
+
module: 'HTTP',
|
|
263
|
+
sessionId,
|
|
264
|
+
error: error instanceof Error ? error.message : String(error),
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
this.sseTransports.clear()
|
|
269
|
+
|
|
270
|
+
this.sessionLastActivity.clear()
|
|
271
|
+
if (this.sessionSweepTimer !== null) {
|
|
272
|
+
clearInterval(this.sessionSweepTimer)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (this.httpServer !== null) {
|
|
276
|
+
this.httpServer.close()
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
logger.info('Shutdown complete', { module: 'HTTP' })
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// =========================================================================
|
|
283
|
+
// Stateless Mode
|
|
284
|
+
// =========================================================================
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Setup stateless transport (single transport, no session management)
|
|
288
|
+
*/
|
|
289
|
+
private async setupStateless(server: McpServer): Promise<void> {
|
|
290
|
+
const statelessTransport = new StreamableHTTPServerTransport({
|
|
291
|
+
sessionIdGenerator: undefined,
|
|
292
|
+
enableJsonResponse: true,
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
await server.connect(statelessTransport)
|
|
296
|
+
logger.info('Stateless transport connected', { module: 'HTTP' })
|
|
297
|
+
|
|
298
|
+
// POST /mcp — all requests go to the same transport
|
|
299
|
+
this.app.post('/mcp', (req: Request, res: Response): void => {
|
|
300
|
+
void statelessTransport.handleRequest(
|
|
301
|
+
req as unknown as IncomingMessage,
|
|
302
|
+
res as unknown as ServerResponse,
|
|
303
|
+
req.body as unknown
|
|
304
|
+
)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
// GET /mcp — SSE not available in stateless mode
|
|
308
|
+
this.app.get('/mcp', (_req: Request, res: Response): void => {
|
|
309
|
+
res.status(405).json({
|
|
310
|
+
jsonrpc: '2.0',
|
|
311
|
+
error: {
|
|
312
|
+
code: -32000,
|
|
313
|
+
message: 'SSE streaming not available in stateless mode',
|
|
314
|
+
},
|
|
315
|
+
id: null,
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
// DELETE /mcp — no-op in stateless mode
|
|
320
|
+
this.app.delete('/mcp', (_req: Request, res: Response): void => {
|
|
321
|
+
res.status(204).end()
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// =========================================================================
|
|
326
|
+
// Stateful Mode
|
|
327
|
+
// =========================================================================
|
|
328
|
+
|
|
329
|
+
/** Update the last-activity timestamp for a session */
|
|
330
|
+
private touchSession(sid: string): void {
|
|
331
|
+
this.sessionLastActivity.set(sid, Date.now())
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Setup stateful transport (multi-session, SSE streaming)
|
|
336
|
+
*/
|
|
337
|
+
private setupStateful(server: McpServer): void {
|
|
338
|
+
// Start session timeout sweep (runs every 5 minutes)
|
|
339
|
+
this.sessionSweepTimer = setInterval(() => {
|
|
340
|
+
const now = Date.now()
|
|
341
|
+
for (const [sid, lastActivity] of this.sessionLastActivity) {
|
|
342
|
+
const idleMs = now - lastActivity
|
|
343
|
+
if (idleMs <= SESSION_TIMEOUT_MS) continue
|
|
344
|
+
|
|
345
|
+
// Expire idle Streamable HTTP sessions
|
|
346
|
+
if (this.transports.has(sid)) {
|
|
347
|
+
logger.info('Expiring idle HTTP session', {
|
|
348
|
+
module: 'HTTP',
|
|
349
|
+
sessionId: sid,
|
|
350
|
+
idleMinutes: Math.round(idleMs / 60_000),
|
|
351
|
+
})
|
|
352
|
+
const t = this.transports.get(sid)
|
|
353
|
+
if (t) {
|
|
354
|
+
void t.close()
|
|
355
|
+
}
|
|
356
|
+
this.transports.delete(sid)
|
|
357
|
+
this.sessionLastActivity.delete(sid)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Expire idle Legacy SSE sessions
|
|
361
|
+
if (this.sseTransports.has(sid)) {
|
|
362
|
+
logger.info('Expiring idle SSE session', {
|
|
363
|
+
module: 'HTTP',
|
|
364
|
+
sessionId: sid,
|
|
365
|
+
idleMinutes: Math.round(idleMs / 60_000),
|
|
366
|
+
})
|
|
367
|
+
const t = this.sseTransports.get(sid)
|
|
368
|
+
if (t) {
|
|
369
|
+
void t.close()
|
|
370
|
+
}
|
|
371
|
+
this.sseTransports.delete(sid)
|
|
372
|
+
this.sessionLastActivity.delete(sid)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}, SESSION_SWEEP_INTERVAL_MS)
|
|
376
|
+
|
|
377
|
+
// POST /mcp — Handle JSON-RPC requests
|
|
378
|
+
this.app.post('/mcp', (req: Request, res: Response): void => {
|
|
379
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined
|
|
380
|
+
|
|
381
|
+
void (async () => {
|
|
382
|
+
try {
|
|
383
|
+
let httpTransport: StreamableHTTPServerTransport | undefined
|
|
384
|
+
|
|
385
|
+
if (sessionId !== undefined && this.transports.has(sessionId)) {
|
|
386
|
+
// Cross-protocol guard: reject SSE session IDs on /mcp
|
|
387
|
+
if (this.sseTransports.has(sessionId)) {
|
|
388
|
+
res.status(400).json({
|
|
389
|
+
jsonrpc: '2.0',
|
|
390
|
+
error: {
|
|
391
|
+
code: -32000,
|
|
392
|
+
message:
|
|
393
|
+
'Bad Request: Session uses Legacy SSE transport, not Streamable HTTP',
|
|
394
|
+
},
|
|
395
|
+
id: null,
|
|
396
|
+
})
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Reuse existing transport and refresh session activity
|
|
401
|
+
this.touchSession(sessionId)
|
|
402
|
+
httpTransport = this.transports.get(sessionId)
|
|
403
|
+
} else if (sessionId === undefined && isInitializeRequest(req.body)) {
|
|
404
|
+
// New initialization request — create transport
|
|
405
|
+
const newTransport = new StreamableHTTPServerTransport({
|
|
406
|
+
sessionIdGenerator: () => randomUUID(),
|
|
407
|
+
onsessioninitialized: (sid: string) => {
|
|
408
|
+
logger.info('HTTP session initialized', {
|
|
409
|
+
module: 'HTTP',
|
|
410
|
+
sessionId: sid,
|
|
411
|
+
})
|
|
412
|
+
this.transports.set(sid, newTransport)
|
|
413
|
+
this.touchSession(sid)
|
|
414
|
+
},
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
// Clean up on transport close
|
|
418
|
+
newTransport.onclose = () => {
|
|
419
|
+
const sid = newTransport.sessionId
|
|
420
|
+
if (sid !== undefined && this.transports.has(sid)) {
|
|
421
|
+
logger.info('HTTP transport closed', {
|
|
422
|
+
module: 'HTTP',
|
|
423
|
+
sessionId: sid,
|
|
424
|
+
})
|
|
425
|
+
this.transports.delete(sid)
|
|
426
|
+
this.sessionLastActivity.delete(sid)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Connect transport to server
|
|
431
|
+
// SDK McpServer only supports one active transport — close first
|
|
432
|
+
try {
|
|
433
|
+
await server.connect(newTransport)
|
|
434
|
+
} catch {
|
|
435
|
+
await server.close()
|
|
436
|
+
await server.connect(newTransport)
|
|
437
|
+
}
|
|
438
|
+
await newTransport.handleRequest(
|
|
439
|
+
req as unknown as IncomingMessage,
|
|
440
|
+
res as unknown as ServerResponse,
|
|
441
|
+
req.body as unknown
|
|
442
|
+
)
|
|
443
|
+
return
|
|
444
|
+
} else {
|
|
445
|
+
// Invalid request — no session ID or not initialization
|
|
446
|
+
res.status(400).json({
|
|
447
|
+
jsonrpc: '2.0',
|
|
448
|
+
error: {
|
|
449
|
+
code: -32000,
|
|
450
|
+
message: 'Bad Request: No valid session ID provided',
|
|
451
|
+
},
|
|
452
|
+
id: null,
|
|
453
|
+
})
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Handle request with existing transport
|
|
458
|
+
if (httpTransport !== undefined) {
|
|
459
|
+
await httpTransport.handleRequest(
|
|
460
|
+
req as unknown as IncomingMessage,
|
|
461
|
+
res as unknown as ServerResponse,
|
|
462
|
+
req.body as unknown
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
logger.error('Error handling MCP request', {
|
|
467
|
+
module: 'HTTP',
|
|
468
|
+
error: error instanceof Error ? error.message : String(error),
|
|
469
|
+
})
|
|
470
|
+
if (!res.headersSent) {
|
|
471
|
+
res.status(500).json({
|
|
472
|
+
jsonrpc: '2.0',
|
|
473
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
474
|
+
id: null,
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
})()
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
// GET /mcp — SSE stream for server-to-client notifications
|
|
482
|
+
this.app.get('/mcp', (req: Request, res: Response): void => {
|
|
483
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined
|
|
484
|
+
|
|
485
|
+
if (sessionId === undefined || !this.transports.has(sessionId)) {
|
|
486
|
+
res.status(400).send('Invalid or missing session ID')
|
|
487
|
+
return
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Refresh session activity on SSE reconnect
|
|
491
|
+
this.touchSession(sessionId)
|
|
492
|
+
|
|
493
|
+
const lastEventId = req.headers['last-event-id']
|
|
494
|
+
if (lastEventId !== undefined) {
|
|
495
|
+
logger.debug('Client reconnecting with Last-Event-ID', {
|
|
496
|
+
module: 'HTTP',
|
|
497
|
+
sessionId,
|
|
498
|
+
lastEventId,
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const httpTransport = this.transports.get(sessionId)
|
|
503
|
+
if (httpTransport !== undefined) {
|
|
504
|
+
void httpTransport.handleRequest(
|
|
505
|
+
req as unknown as IncomingMessage,
|
|
506
|
+
res as unknown as ServerResponse
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
// DELETE /mcp — Session termination
|
|
512
|
+
this.app.delete('/mcp', (req: Request, res: Response): void => {
|
|
513
|
+
const sessionId = req.headers['mcp-session-id'] as string | undefined
|
|
514
|
+
|
|
515
|
+
if (sessionId === undefined || !this.transports.has(sessionId)) {
|
|
516
|
+
res.status(400).send('Invalid or missing session ID')
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
logger.info('Session termination requested', {
|
|
521
|
+
module: 'HTTP',
|
|
522
|
+
sessionId,
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
const httpTransport = this.transports.get(sessionId)
|
|
526
|
+
if (httpTransport !== undefined) {
|
|
527
|
+
void httpTransport.handleRequest(
|
|
528
|
+
req as unknown as IncomingMessage,
|
|
529
|
+
res as unknown as ServerResponse
|
|
530
|
+
)
|
|
531
|
+
}
|
|
532
|
+
})
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// =========================================================================
|
|
536
|
+
// Legacy SSE (MCP 2024-11-05)
|
|
537
|
+
// =========================================================================
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Setup Legacy SSE endpoints for backward compatibility.
|
|
541
|
+
* Stateful mode only.
|
|
542
|
+
*/
|
|
543
|
+
private setupLegacySSE(server: McpServer): void {
|
|
544
|
+
// GET /sse — Open Legacy SSE connection
|
|
545
|
+
this.app.get('/sse', (req: Request, res: Response): void => {
|
|
546
|
+
logger.info('Legacy SSE connection requested', { module: 'HTTP' })
|
|
547
|
+
|
|
548
|
+
const sseTransport = new SSEServerTransport(
|
|
549
|
+
'/messages',
|
|
550
|
+
res as unknown as ServerResponse
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
// Store transport by session ID after start
|
|
554
|
+
sseTransport.onclose = () => {
|
|
555
|
+
logger.info('Legacy SSE transport closed', {
|
|
556
|
+
module: 'HTTP',
|
|
557
|
+
sessionId: sseTransport.sessionId,
|
|
558
|
+
})
|
|
559
|
+
this.sseTransports.delete(sseTransport.sessionId)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
void (async () => {
|
|
563
|
+
try {
|
|
564
|
+
// Connect SSE transport to server
|
|
565
|
+
// SDK McpServer only supports one active transport — close first
|
|
566
|
+
try {
|
|
567
|
+
await server.connect(
|
|
568
|
+
sseTransport as unknown as Parameters<typeof server.connect>[0]
|
|
569
|
+
)
|
|
570
|
+
} catch {
|
|
571
|
+
await server.close()
|
|
572
|
+
await server.connect(
|
|
573
|
+
sseTransport as unknown as Parameters<typeof server.connect>[0]
|
|
574
|
+
)
|
|
575
|
+
}
|
|
576
|
+
// Note: server.connect() auto-calls start() on SSEServerTransport
|
|
577
|
+
this.sseTransports.set(sseTransport.sessionId, sseTransport)
|
|
578
|
+
this.touchSession(sseTransport.sessionId)
|
|
579
|
+
logger.info('Legacy SSE connection established', {
|
|
580
|
+
module: 'HTTP',
|
|
581
|
+
sessionId: sseTransport.sessionId,
|
|
582
|
+
})
|
|
583
|
+
} catch (error) {
|
|
584
|
+
logger.error('Error starting SSE transport', {
|
|
585
|
+
module: 'HTTP',
|
|
586
|
+
error: error instanceof Error ? error.message : String(error),
|
|
587
|
+
})
|
|
588
|
+
if (!res.headersSent) {
|
|
589
|
+
res.status(500).end()
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
})()
|
|
593
|
+
|
|
594
|
+
// Clean up when client disconnects
|
|
595
|
+
req.on('close', () => {
|
|
596
|
+
this.sseTransports.delete(sseTransport.sessionId)
|
|
597
|
+
this.sessionLastActivity.delete(sseTransport.sessionId)
|
|
598
|
+
})
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
// POST /messages?sessionId=<id> — Route messages to Legacy SSE transport
|
|
602
|
+
this.app.post('/messages', (req: Request, res: Response): void => {
|
|
603
|
+
const sessionId =
|
|
604
|
+
typeof req.query['sessionId'] === 'string' ? req.query['sessionId'] : undefined
|
|
605
|
+
|
|
606
|
+
if (sessionId === undefined) {
|
|
607
|
+
res.status(400).json({
|
|
608
|
+
jsonrpc: '2.0',
|
|
609
|
+
error: { code: -32000, message: 'Missing sessionId parameter' },
|
|
610
|
+
id: null,
|
|
611
|
+
})
|
|
612
|
+
return
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const transport = this.sseTransports.get(sessionId)
|
|
616
|
+
if (transport === undefined) {
|
|
617
|
+
res.status(404).json({
|
|
618
|
+
jsonrpc: '2.0',
|
|
619
|
+
error: { code: -32000, message: 'Session not found' },
|
|
620
|
+
id: null,
|
|
621
|
+
})
|
|
622
|
+
return
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Refresh session activity on message receipt
|
|
626
|
+
this.touchSession(sessionId)
|
|
627
|
+
|
|
628
|
+
void transport.handlePostMessage(
|
|
629
|
+
req as unknown as IncomingMessage,
|
|
630
|
+
res as unknown as ServerResponse,
|
|
631
|
+
req.body as unknown
|
|
632
|
+
)
|
|
633
|
+
})
|
|
634
|
+
}
|
|
635
|
+
}
|