@cyanheads/pubmed-mcp-server 1.2.4 → 1.3.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 (91) hide show
  1. package/README.md +2 -2
  2. package/dist/config/index.d.ts +13 -52
  3. package/dist/config/index.js +51 -222
  4. package/dist/mcp-server/server.d.ts +0 -5
  5. package/dist/mcp-server/server.js +18 -34
  6. package/dist/mcp-server/tools/fetchPubMedContent/logic.js +2 -2
  7. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +2 -2
  8. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +3 -3
  9. package/dist/mcp-server/tools/searchPubMedArticles/logic.js +2 -2
  10. package/dist/mcp-server/transports/auth/authFactory.d.ts +10 -0
  11. package/dist/mcp-server/transports/auth/authFactory.js +41 -0
  12. package/dist/mcp-server/transports/auth/authMiddleware.d.ts +19 -0
  13. package/dist/mcp-server/transports/auth/authMiddleware.js +57 -0
  14. package/dist/mcp-server/transports/auth/index.d.ts +8 -5
  15. package/dist/mcp-server/transports/auth/index.js +6 -4
  16. package/dist/mcp-server/transports/auth/{core → lib}/authTypes.d.ts +0 -5
  17. package/dist/mcp-server/transports/auth/lib/authTypes.js +8 -0
  18. package/dist/mcp-server/transports/auth/{core → lib}/authUtils.js +21 -14
  19. package/dist/mcp-server/transports/auth/strategies/authStrategy.d.ts +17 -0
  20. package/dist/mcp-server/transports/auth/strategies/authStrategy.js +1 -0
  21. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts +7 -0
  22. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +112 -0
  23. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts +7 -0
  24. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +101 -0
  25. package/dist/mcp-server/transports/core/baseTransportManager.d.ts +17 -0
  26. package/dist/mcp-server/transports/core/baseTransportManager.js +18 -0
  27. package/dist/mcp-server/transports/core/honoNodeBridge.d.ts +23 -0
  28. package/dist/mcp-server/transports/core/honoNodeBridge.js +51 -0
  29. package/dist/mcp-server/transports/core/statefulTransportManager.d.ts +31 -0
  30. package/dist/mcp-server/transports/core/statefulTransportManager.js +233 -0
  31. package/dist/mcp-server/transports/core/statelessTransportManager.d.ts +20 -0
  32. package/dist/mcp-server/transports/core/statelessTransportManager.js +92 -0
  33. package/dist/mcp-server/transports/core/transportTypes.d.ts +68 -0
  34. package/dist/mcp-server/transports/core/transportTypes.js +5 -0
  35. package/dist/mcp-server/transports/{httpErrorHandler.d.ts → http/httpErrorHandler.d.ts} +4 -9
  36. package/dist/mcp-server/transports/{httpErrorHandler.js → http/httpErrorHandler.js} +33 -8
  37. package/dist/mcp-server/transports/http/httpTransport.d.ts +22 -0
  38. package/dist/mcp-server/transports/http/httpTransport.js +251 -0
  39. package/dist/mcp-server/transports/http/httpTypes.d.ts +16 -0
  40. package/dist/mcp-server/transports/http/httpTypes.js +5 -0
  41. package/dist/mcp-server/transports/http/index.d.ts +7 -0
  42. package/dist/mcp-server/transports/http/index.js +6 -0
  43. package/dist/mcp-server/transports/http/mcpTransportMiddleware.d.ts +25 -0
  44. package/dist/mcp-server/transports/http/mcpTransportMiddleware.js +63 -0
  45. package/dist/mcp-server/transports/stdio/index.d.ts +5 -0
  46. package/dist/mcp-server/transports/stdio/index.js +5 -0
  47. package/dist/mcp-server/transports/{stdioTransport.d.ts → stdio/stdioTransport.d.ts} +2 -2
  48. package/dist/mcp-server/transports/{stdioTransport.js → stdio/stdioTransport.js} +10 -5
  49. package/dist/services/NCBI/{ncbiConstants.d.ts → core/ncbiConstants.d.ts} +1 -1
  50. package/dist/services/NCBI/{ncbiConstants.js → core/ncbiConstants.js} +1 -1
  51. package/dist/services/NCBI/{ncbiCoreApiClient.d.ts → core/ncbiCoreApiClient.d.ts} +2 -2
  52. package/dist/services/NCBI/{ncbiCoreApiClient.js → core/ncbiCoreApiClient.js} +4 -4
  53. package/dist/services/NCBI/{ncbiRequestQueueManager.d.ts → core/ncbiRequestQueueManager.d.ts} +2 -2
  54. package/dist/services/NCBI/{ncbiRequestQueueManager.js → core/ncbiRequestQueueManager.js} +3 -3
  55. package/dist/services/NCBI/{ncbiResponseHandler.d.ts → core/ncbiResponseHandler.d.ts} +2 -2
  56. package/dist/services/NCBI/{ncbiResponseHandler.js → core/ncbiResponseHandler.js} +3 -3
  57. package/dist/services/NCBI/{ncbiService.d.ts → core/ncbiService.d.ts} +3 -3
  58. package/dist/services/NCBI/{ncbiService.js → core/ncbiService.js} +2 -2
  59. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/eSummaryResultParser.d.ts +1 -1
  60. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/eSummaryResultParser.js +1 -1
  61. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/index.d.ts +1 -1
  62. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/index.js +1 -1
  63. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/pubmedArticleStructureParser.d.ts +1 -1
  64. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/pubmedArticleStructureParser.js +1 -1
  65. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/xmlGenericHelpers.d.ts +1 -1
  66. package/dist/{utils/parsing/ncbi-parsing → services/NCBI/parsing}/xmlGenericHelpers.js +1 -1
  67. package/dist/types-global/errors.d.ts +2 -0
  68. package/dist/types-global/errors.js +2 -0
  69. package/dist/utils/internal/errorHandler.js +1 -1
  70. package/dist/utils/internal/logger.d.ts +13 -1
  71. package/dist/utils/internal/logger.js +43 -9
  72. package/dist/utils/network/fetchWithTimeout.d.ts +21 -0
  73. package/dist/utils/network/fetchWithTimeout.js +59 -0
  74. package/dist/utils/network/index.d.ts +6 -0
  75. package/dist/utils/network/index.js +5 -0
  76. package/dist/utils/scheduling/index.d.ts +6 -0
  77. package/dist/utils/scheduling/index.js +6 -0
  78. package/dist/utils/scheduling/scheduler.d.ts +72 -0
  79. package/dist/utils/scheduling/scheduler.js +150 -0
  80. package/dist/utils/security/sanitization.js +35 -18
  81. package/package.json +9 -7
  82. package/dist/mcp-server/transports/auth/core/authTypes.js +0 -5
  83. package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.d.ts +0 -27
  84. package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +0 -149
  85. package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.d.ts +0 -20
  86. package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +0 -124
  87. package/dist/mcp-server/transports/httpTransport.d.ts +0 -21
  88. package/dist/mcp-server/transports/httpTransport.js +0 -208
  89. /package/dist/mcp-server/transports/auth/{core → lib}/authContext.d.ts +0 -0
  90. /package/dist/mcp-server/transports/auth/{core → lib}/authContext.js +0 -0
  91. /package/dist/mcp-server/transports/auth/{core → lib}/authUtils.d.ts +0 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @fileoverview Stateful Transport Manager implementation for MCP SDK.
3
+ * This manager handles multiple, persistent sessions, creating a dedicated
4
+ * McpServer and StreamableHTTPServerTransport instance for each one.
5
+ * This version is adapted for Hono by bridging the SDK's Node.js-style
6
+ * request handling with Hono's stream-based response model.
7
+ * @module src/mcp-server/transports/core/statefulTransportManager
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import type { IncomingHttpHeaders } from "http";
11
+ import { RequestContext } from "../../../utils/index.js";
12
+ import { BaseTransportManager } from "./baseTransportManager.js";
13
+ import { StatefulTransportManager as IStatefulTransportManager, TransportResponse, TransportSession } from "./transportTypes.js";
14
+ /**
15
+ * Stateful Transport Manager that handles MCP SDK integration and session management
16
+ * for a Hono-based HTTP server.
17
+ */
18
+ export declare class StatefulTransportManager extends BaseTransportManager implements IStatefulTransportManager {
19
+ private readonly transports;
20
+ private readonly servers;
21
+ private readonly sessions;
22
+ private readonly garbageCollector;
23
+ constructor(createServerInstanceFn: () => Promise<McpServer>);
24
+ initializeAndHandle(headers: IncomingHttpHeaders, body: unknown, context: RequestContext): Promise<TransportResponse>;
25
+ handleRequest(headers: IncomingHttpHeaders, body: unknown, context: RequestContext, sessionId?: string): Promise<TransportResponse>;
26
+ handleDeleteRequest(sessionId: string, context: RequestContext): Promise<TransportResponse>;
27
+ getSession(sessionId: string): TransportSession | undefined;
28
+ shutdown(): Promise<void>;
29
+ private closeSession;
30
+ private cleanupStaleSessions;
31
+ }
@@ -0,0 +1,233 @@
1
+ /**
2
+ * @fileoverview Stateful Transport Manager implementation for MCP SDK.
3
+ * This manager handles multiple, persistent sessions, creating a dedicated
4
+ * McpServer and StreamableHTTPServerTransport instance for each one.
5
+ * This version is adapted for Hono by bridging the SDK's Node.js-style
6
+ * request handling with Hono's stream-based response model.
7
+ * @module src/mcp-server/transports/core/statefulTransportManager
8
+ */
9
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
10
+ import { randomUUID } from "node:crypto";
11
+ import { Readable } from "stream";
12
+ import { config } from "../../../config/index.js";
13
+ import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
14
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
15
+ import { BaseTransportManager } from "./baseTransportManager.js";
16
+ import { HonoStreamResponse } from "./honoNodeBridge.js";
17
+ /**
18
+ * Stateful Transport Manager that handles MCP SDK integration and session management
19
+ * for a Hono-based HTTP server.
20
+ */
21
+ export class StatefulTransportManager extends BaseTransportManager {
22
+ constructor(createServerInstanceFn) {
23
+ super(createServerInstanceFn);
24
+ this.transports = new Map();
25
+ this.servers = new Map();
26
+ this.sessions = new Map();
27
+ const context = requestContextService.createRequestContext({
28
+ operation: "StatefulTransportManager.constructor",
29
+ });
30
+ logger.info("Starting session garbage collector.", context);
31
+ this.garbageCollector = setInterval(() => this.cleanupStaleSessions(), config.mcpStatefulSessionStaleTimeoutMs);
32
+ }
33
+ async initializeAndHandle(headers, body, context) {
34
+ const operationName = "StatefulTransportManager.initializeAndHandle";
35
+ const opContext = { ...context, operation: operationName };
36
+ logger.debug("Initializing new stateful session.", opContext);
37
+ const server = await this.createServerInstanceFn();
38
+ const mockRes = new HonoStreamResponse();
39
+ const transport = new StreamableHTTPServerTransport({
40
+ sessionIdGenerator: () => randomUUID(),
41
+ onsessioninitialized: (sessionId) => {
42
+ const sessionContext = { ...opContext, sessionId };
43
+ this.transports.set(sessionId, transport);
44
+ this.servers.set(sessionId, server);
45
+ this.sessions.set(sessionId, {
46
+ id: sessionId,
47
+ createdAt: new Date(),
48
+ lastAccessedAt: new Date(),
49
+ });
50
+ logger.info(`MCP Session created: ${sessionId}`, sessionContext);
51
+ },
52
+ });
53
+ transport.onclose = () => {
54
+ const sessionId = transport.sessionId;
55
+ if (sessionId) {
56
+ const closeContext = requestContextService.createRequestContext({
57
+ operation: "StatefulTransportManager.transport.onclose",
58
+ sessionId,
59
+ });
60
+ this.closeSession(sessionId, closeContext).catch((err) => logger.error(`Error during transport.onclose cleanup for session ${sessionId}`, err, closeContext));
61
+ }
62
+ };
63
+ await server.connect(transport);
64
+ logger.debug("Server connected to transport, handling initial request.", opContext);
65
+ const mockReq = {
66
+ headers,
67
+ method: "POST",
68
+ url: config.mcpHttpEndpointPath,
69
+ };
70
+ await transport.handleRequest(mockReq, mockRes, body);
71
+ const responseHeaders = new Headers();
72
+ for (const [key, value] of Object.entries(mockRes.getHeaders())) {
73
+ responseHeaders.set(key, Array.isArray(value) ? value.join(", ") : String(value));
74
+ }
75
+ if (transport.sessionId) {
76
+ responseHeaders.set("Mcp-Session-Id", transport.sessionId);
77
+ }
78
+ const webStream = Readable.toWeb(mockRes);
79
+ return {
80
+ headers: responseHeaders,
81
+ statusCode: mockRes.statusCode,
82
+ stream: webStream,
83
+ sessionId: transport.sessionId,
84
+ };
85
+ }
86
+ async handleRequest(headers, body, context, sessionId) {
87
+ if (!sessionId) {
88
+ throw new McpError(BaseErrorCode.INVALID_INPUT, "Session ID is required for stateful requests.", context);
89
+ }
90
+ const sessionContext = {
91
+ ...context,
92
+ sessionId,
93
+ operation: "StatefulTransportManager.handleRequest",
94
+ };
95
+ logger.debug(`Handling request for session: ${sessionId}`, {
96
+ ...sessionContext,
97
+ method: headers["x-forwarded-proto"] || "http",
98
+ });
99
+ const transport = this.transports.get(sessionId);
100
+ if (!transport) {
101
+ logger.warning(`Request for non-existent session: ${sessionId}`, sessionContext);
102
+ return {
103
+ headers: new Headers({ "Content-Type": "application/json" }),
104
+ statusCode: 404,
105
+ body: {
106
+ jsonrpc: "2.0",
107
+ error: { code: -32601, message: "Session not found" },
108
+ },
109
+ };
110
+ }
111
+ const session = this.sessions.get(sessionId);
112
+ if (session) {
113
+ session.lastAccessedAt = new Date();
114
+ logger.debug(`Updated lastAccessedAt for session ${sessionId}.`, sessionContext);
115
+ }
116
+ const mockReq = {
117
+ headers,
118
+ method: "POST",
119
+ };
120
+ const mockRes = new HonoStreamResponse();
121
+ await transport.handleRequest(mockReq, mockRes, body);
122
+ const responseHeaders = new Headers();
123
+ for (const [key, value] of Object.entries(mockRes.getHeaders())) {
124
+ responseHeaders.set(key, Array.isArray(value) ? value.join(", ") : String(value));
125
+ }
126
+ const webStream = Readable.toWeb(mockRes);
127
+ return {
128
+ headers: responseHeaders,
129
+ statusCode: mockRes.statusCode,
130
+ stream: webStream,
131
+ sessionId: transport.sessionId,
132
+ };
133
+ }
134
+ async handleDeleteRequest(sessionId, context) {
135
+ const sessionContext = {
136
+ ...context,
137
+ sessionId,
138
+ operation: "StatefulTransportManager.handleDeleteRequest",
139
+ };
140
+ logger.info(`Attempting to delete session: ${sessionId}`, sessionContext);
141
+ const transport = this.transports.get(sessionId);
142
+ if (!transport) {
143
+ logger.warning(`Attempted to delete non-existent session: ${sessionId}`, sessionContext);
144
+ throw new McpError(BaseErrorCode.NOT_FOUND, "Session not found or expired.", sessionContext);
145
+ }
146
+ await this.closeSession(sessionId, sessionContext);
147
+ const headers = new Headers();
148
+ headers.set("Content-Type", "application/json");
149
+ return {
150
+ headers,
151
+ statusCode: 200,
152
+ body: { status: "session_closed", sessionId },
153
+ };
154
+ }
155
+ getSession(sessionId) {
156
+ const context = requestContextService.createRequestContext({
157
+ operation: "StatefulTransportManager.getSession",
158
+ sessionId,
159
+ });
160
+ logger.debug(`Retrieving session: ${sessionId}`, context);
161
+ return this.sessions.get(sessionId);
162
+ }
163
+ async shutdown() {
164
+ const context = requestContextService.createRequestContext({
165
+ operation: "StatefulTransportManager.shutdown",
166
+ });
167
+ logger.info("Shutting down stateful transport manager...", context);
168
+ clearInterval(this.garbageCollector);
169
+ logger.debug("Garbage collector stopped.", context);
170
+ const sessionIds = Array.from(this.transports.keys());
171
+ logger.info(`Closing ${sessionIds.length} active sessions.`, context);
172
+ const closePromises = sessionIds.map((sessionId) => this.closeSession(sessionId, context));
173
+ await Promise.all(closePromises);
174
+ this.transports.clear();
175
+ this.sessions.clear();
176
+ this.servers.clear();
177
+ logger.info("All active sessions closed and manager shut down.", context);
178
+ }
179
+ async closeSession(sessionId, context) {
180
+ const sessionContext = {
181
+ ...context,
182
+ sessionId,
183
+ operation: "StatefulTransportManager.closeSession",
184
+ };
185
+ logger.debug(`Closing session: ${sessionId}`, sessionContext);
186
+ const transport = this.transports.get(sessionId);
187
+ const server = this.servers.get(sessionId);
188
+ await ErrorHandler.tryCatch(async () => {
189
+ if (transport) {
190
+ await transport.close();
191
+ logger.debug(`Transport closed for session ${sessionId}.`, sessionContext);
192
+ }
193
+ if (server) {
194
+ await server.close();
195
+ logger.debug(`Server instance closed for session ${sessionId}.`, sessionContext);
196
+ }
197
+ }, {
198
+ operation: "closeSession.cleanup",
199
+ context: sessionContext,
200
+ });
201
+ this.transports.delete(sessionId);
202
+ this.servers.delete(sessionId);
203
+ this.sessions.delete(sessionId);
204
+ logger.info(`MCP Session closed and resources released: ${sessionId}`, sessionContext);
205
+ }
206
+ async cleanupStaleSessions() {
207
+ const context = requestContextService.createRequestContext({
208
+ operation: "StatefulTransportManager.cleanupStaleSessions",
209
+ });
210
+ logger.debug("Running stale session cleanup...", context);
211
+ const now = Date.now();
212
+ const STALE_TIMEOUT_MS = config.mcpStatefulSessionStaleTimeoutMs;
213
+ let staleCount = 0;
214
+ for (const [sessionId, session] of this.sessions.entries()) {
215
+ if (now - session.lastAccessedAt.getTime() > STALE_TIMEOUT_MS) {
216
+ staleCount++;
217
+ const sessionContext = {
218
+ ...context,
219
+ sessionId,
220
+ lastAccessed: session.lastAccessedAt.toISOString(),
221
+ };
222
+ logger.info(`Found stale session, closing: ${sessionId}`, sessionContext);
223
+ await this.closeSession(sessionId, sessionContext);
224
+ }
225
+ }
226
+ if (staleCount > 0) {
227
+ logger.info(`Stale session cleanup complete. Closed ${staleCount} sessions.`, context);
228
+ }
229
+ else {
230
+ logger.debug("No stale sessions found.", context);
231
+ }
232
+ }
233
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @fileoverview Stateless Transport Manager implementation for MCP SDK.
3
+ * This manager handles single-request operations without maintaining sessions.
4
+ * Each request creates a temporary server instance that is cleaned up immediately.
5
+ * This version is adapted for Hono by bridging the SDK's Node.js-style
6
+ * request handling with Hono's stream-based response model.
7
+ * @module src/mcp-server/transports/core/statelessTransportManager
8
+ */
9
+ import type { IncomingHttpHeaders } from "http";
10
+ import { RequestContext } from "../../../utils/index.js";
11
+ import { BaseTransportManager } from "./baseTransportManager.js";
12
+ import { TransportResponse } from "./transportTypes.js";
13
+ /**
14
+ * Stateless Transport Manager that handles ephemeral MCP operations.
15
+ */
16
+ export declare class StatelessTransportManager extends BaseTransportManager {
17
+ handleRequest(headers: IncomingHttpHeaders, body: unknown, context: RequestContext): Promise<TransportResponse>;
18
+ shutdown(): Promise<void>;
19
+ private cleanup;
20
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @fileoverview Stateless Transport Manager implementation for MCP SDK.
3
+ * This manager handles single-request operations without maintaining sessions.
4
+ * Each request creates a temporary server instance that is cleaned up immediately.
5
+ * This version is adapted for Hono by bridging the SDK's Node.js-style
6
+ * request handling with Hono's stream-based response model.
7
+ * @module src/mcp-server/transports/core/statelessTransportManager
8
+ */
9
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
10
+ import { Readable } from "stream";
11
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
12
+ import { BaseTransportManager } from "./baseTransportManager.js";
13
+ import { HonoStreamResponse } from "./honoNodeBridge.js";
14
+ /**
15
+ * Stateless Transport Manager that handles ephemeral MCP operations.
16
+ */
17
+ export class StatelessTransportManager extends BaseTransportManager {
18
+ async handleRequest(headers, body, context) {
19
+ const opContext = {
20
+ ...context,
21
+ operation: "StatelessTransportManager.handleRequest",
22
+ };
23
+ logger.debug("Creating ephemeral server instance for stateless request.", opContext);
24
+ let server;
25
+ let transport;
26
+ try {
27
+ server = await this.createServerInstanceFn();
28
+ transport = new StreamableHTTPServerTransport({
29
+ sessionIdGenerator: undefined,
30
+ onsessioninitialized: undefined,
31
+ });
32
+ await server.connect(transport);
33
+ logger.debug("Ephemeral server connected to transport.", opContext);
34
+ const mockReq = {
35
+ headers,
36
+ method: "POST",
37
+ };
38
+ const mockRes = new HonoStreamResponse();
39
+ await transport.handleRequest(mockReq, mockRes, body);
40
+ logger.info("Stateless request handled successfully.", opContext);
41
+ const responseHeaders = new Headers();
42
+ for (const [key, value] of Object.entries(mockRes.getHeaders())) {
43
+ responseHeaders.set(key, Array.isArray(value) ? value.join(", ") : String(value));
44
+ }
45
+ // Bridge the Node.js stream (PassThrough) to a Web Stream (ReadableStream)
46
+ const webStream = Readable.toWeb(mockRes);
47
+ return {
48
+ headers: responseHeaders,
49
+ statusCode: mockRes.statusCode,
50
+ stream: webStream,
51
+ };
52
+ }
53
+ catch (error) {
54
+ throw ErrorHandler.handleError(error, {
55
+ operation: "StatelessTransportManager.handleRequest",
56
+ context: opContext,
57
+ rethrow: true,
58
+ });
59
+ }
60
+ finally {
61
+ if (server || transport) {
62
+ this.cleanup(server, transport, opContext);
63
+ }
64
+ }
65
+ }
66
+ async shutdown() {
67
+ const context = requestContextService.createRequestContext({
68
+ operation: "StatelessTransportManager.shutdown",
69
+ });
70
+ logger.info("Stateless transport manager shutdown - no persistent resources to clean up.", context);
71
+ return Promise.resolve();
72
+ }
73
+ cleanup(server, transport, context) {
74
+ const opContext = {
75
+ ...context,
76
+ operation: "StatelessTransportManager.cleanup",
77
+ };
78
+ logger.debug("Scheduling cleanup for ephemeral resources.", opContext);
79
+ Promise.all([transport?.close(), server?.close()])
80
+ .then(() => {
81
+ logger.debug("Ephemeral resources cleaned up successfully.", opContext);
82
+ })
83
+ .catch((cleanupError) => {
84
+ logger.warning("Error during stateless resource cleanup.", {
85
+ ...opContext,
86
+ error: cleanupError instanceof Error
87
+ ? cleanupError.message
88
+ : String(cleanupError),
89
+ });
90
+ });
91
+ }
92
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @fileoverview Core types and interfaces for the transport layer abstraction.
3
+ * @module src/mcp-server/transports/core/transportTypes
4
+ */
5
+ import type { IncomingHttpHeaders } from "http";
6
+ import { RequestContext } from "../../../utils/index.js";
7
+ /**
8
+ * Valid HTTP status codes for transport responses.
9
+ */
10
+ export type HttpStatusCode = 200 | 201 | 400 | 401 | 403 | 404 | 409 | 429 | 500 | 502 | 503;
11
+ /**
12
+ * Represents the result of a transport operation.
13
+ */
14
+ export interface TransportResponse {
15
+ sessionId?: string;
16
+ headers: Headers;
17
+ statusCode: HttpStatusCode;
18
+ body?: unknown;
19
+ stream?: ReadableStream<Uint8Array>;
20
+ }
21
+ /**
22
+ * Represents an active transport session.
23
+ */
24
+ export interface TransportSession {
25
+ id: string;
26
+ createdAt: Date;
27
+ lastAccessedAt: Date;
28
+ }
29
+ /**
30
+ * Abstract interface for managing MCP transport operations.
31
+ * This interface separates transport logic from HTTP routing concerns.
32
+ */
33
+ export interface TransportManager {
34
+ /**
35
+ * Handles an incoming request.
36
+ * @param headers The request headers.
37
+ * @param body The parsed body of the request.
38
+ * @param context Request context for logging and tracing.
39
+ * @param sessionId Optional session identifier for stateful operations.
40
+ * @returns A promise resolving to a TransportResponse.
41
+ */
42
+ handleRequest(headers: IncomingHttpHeaders, body: unknown, context: RequestContext, sessionId?: string): Promise<TransportResponse>;
43
+ /**
44
+ * Clean up resources.
45
+ */
46
+ shutdown(): Promise<void>;
47
+ }
48
+ /**
49
+ * Extends the base TransportManager with session-specific operations.
50
+ */
51
+ export interface StatefulTransportManager extends TransportManager {
52
+ /**
53
+ * Initializes a new session and handles the request.
54
+ * @param headers The request headers.
55
+ * @param body The parsed body of the request.
56
+ * @param context Request context for logging and tracing.
57
+ * @returns A promise resolving to a TransportResponse.
58
+ */
59
+ initializeAndHandle(headers: IncomingHttpHeaders, body: unknown, context: RequestContext): Promise<TransportResponse>;
60
+ /**
61
+ * Handles a DELETE request to close a session.
62
+ */
63
+ handleDeleteRequest(sessionId: string, context: RequestContext): Promise<TransportResponse>;
64
+ /**
65
+ * Retrieves session information.
66
+ */
67
+ getSession(sessionId: string): TransportSession | undefined;
68
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @fileoverview Core types and interfaces for the transport layer abstraction.
3
+ * @module src/mcp-server/transports/core/transportTypes
4
+ */
5
+ export {};
@@ -6,7 +6,7 @@
6
6
  * @module src/mcp-server/transports/httpErrorHandler
7
7
  */
8
8
  import { Context } from "hono";
9
- import { BaseErrorCode } from "../../types-global/errors.js";
9
+ import { HonoNodeBindings } from "./httpTypes.js";
10
10
  /**
11
11
  * A centralized error handling middleware for Hono.
12
12
  * This function is registered with `app.onError()` and will catch any errors
@@ -16,11 +16,6 @@ import { BaseErrorCode } from "../../types-global/errors.js";
16
16
  * @param c - The Hono context object for the request.
17
17
  * @returns A Response object containing the formatted JSON-RPC error.
18
18
  */
19
- export declare const httpErrorHandler: (err: Error, c: Context) => Promise<Response & import("hono").TypedResponse<{
20
- jsonrpc: string;
21
- error: {
22
- code: number | BaseErrorCode;
23
- message: string;
24
- };
25
- id: string | number | null;
26
- }, import("hono/utils/http-status").ContentfulStatusCode, "json">>;
19
+ export declare const httpErrorHandler: (err: Error, c: Context<{
20
+ Bindings: HonoNodeBindings;
21
+ }>) => Promise<Response>;
@@ -5,8 +5,8 @@
5
5
  * formats them into a consistent JSON-RPC error response.
6
6
  * @module src/mcp-server/transports/httpErrorHandler
7
7
  */
8
- import { BaseErrorCode, McpError } from "../../types-global/errors.js";
9
- import { ErrorHandler, requestContextService } from "../../utils/index.js";
8
+ import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
9
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js";
10
10
  /**
11
11
  * A centralized error handling middleware for Hono.
12
12
  * This function is registered with `app.onError()` and will catch any errors
@@ -22,6 +22,7 @@ export const httpErrorHandler = async (err, c) => {
22
22
  path: c.req.path,
23
23
  method: c.req.method,
24
24
  });
25
+ logger.debug("HTTP error handler invoked.", context);
25
26
  const handledError = ErrorHandler.handleError(err, {
26
27
  operation: "httpTransport",
27
28
  context,
@@ -39,6 +40,7 @@ export const httpErrorHandler = async (err, c) => {
39
40
  status = 403;
40
41
  break;
41
42
  case BaseErrorCode.VALIDATION_ERROR:
43
+ case BaseErrorCode.INVALID_INPUT:
42
44
  status = 400;
43
45
  break;
44
46
  case BaseErrorCode.CONFLICT:
@@ -51,23 +53,46 @@ export const httpErrorHandler = async (err, c) => {
51
53
  status = 500;
52
54
  }
53
55
  }
56
+ logger.debug(`Mapping error to HTTP status ${status}.`, {
57
+ ...context,
58
+ status,
59
+ errorCode: handledError.code,
60
+ });
54
61
  // Attempt to get the request ID from the body, but don't fail if it's not there or unreadable.
55
62
  let requestId = null;
56
- try {
57
- const body = await c.req.json();
58
- requestId = body?.id || null;
63
+ // Only attempt to read the body if it hasn't been consumed already.
64
+ if (c.req.raw.bodyUsed === false) {
65
+ try {
66
+ const body = await c.req.json();
67
+ requestId = body?.id || null;
68
+ logger.debug("Extracted JSON-RPC request ID from body.", {
69
+ ...context,
70
+ jsonRpcId: requestId,
71
+ });
72
+ }
73
+ catch {
74
+ logger.warning("Could not parse request body to extract JSON-RPC ID.", context);
75
+ // Ignore parsing errors, requestId will remain null
76
+ }
59
77
  }
60
- catch {
61
- // Ignore parsing errors, requestId will remain null
78
+ else {
79
+ logger.debug("Request body already consumed, cannot extract JSON-RPC ID.", context);
62
80
  }
63
81
  const errorCode = handledError instanceof McpError ? handledError.code : -32603;
64
82
  c.status(status);
65
- return c.json({
83
+ const errorResponse = {
66
84
  jsonrpc: "2.0",
67
85
  error: {
68
86
  code: errorCode,
69
87
  message: handledError.message,
70
88
  },
71
89
  id: requestId,
90
+ };
91
+ logger.info(`Sending formatted error response for request.`, {
92
+ ...context,
93
+ status,
94
+ errorCode,
95
+ jsonRpcId: requestId,
72
96
  });
97
+ return c.json(errorResponse);
73
98
  };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Configures and starts the HTTP MCP transport using Hono.
3
+ * This file has been refactored to correctly integrate Hono's streaming
4
+ * capabilities with the Model Context Protocol SDK's transport layer.
5
+ * @module src/mcp-server/transports/http/httpTransport
6
+ */
7
+ import { ServerType } from "@hono/node-server";
8
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { Hono } from "hono";
10
+ import { RequestContext } from "../../../utils/index.js";
11
+ import { TransportManager } from "../core/transportTypes.js";
12
+ import { HonoNodeBindings } from "./httpTypes.js";
13
+ export declare function createHttpApp(transportManager: TransportManager, createServerInstanceFn: () => Promise<McpServer>, parentContext: RequestContext): Hono<{
14
+ Bindings: HonoNodeBindings;
15
+ }>;
16
+ export declare function startHttpTransport(createServerInstanceFn: () => Promise<McpServer>, parentContext: RequestContext): Promise<{
17
+ app: Hono<{
18
+ Bindings: HonoNodeBindings;
19
+ }>;
20
+ server: ServerType;
21
+ transportManager: TransportManager;
22
+ }>;