streamableHttp.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. "use strict";
  2. /**
  3. * Node.js HTTP Streamable HTTP Server Transport
  4. *
  5. * This is a thin wrapper around `WebStandardStreamableHTTPServerTransport` that provides
  6. * compatibility with Node.js HTTP server (IncomingMessage/ServerResponse).
  7. *
  8. * For web-standard environments (Cloudflare Workers, Deno, Bun), use `WebStandardStreamableHTTPServerTransport` directly.
  9. */
  10. Object.defineProperty(exports, "__esModule", { value: true });
  11. exports.StreamableHTTPServerTransport = void 0;
  12. const node_server_1 = require("@hono/node-server");
  13. const webStandardStreamableHttp_js_1 = require("./webStandardStreamableHttp.js");
  14. /**
  15. * Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
  16. * It supports both SSE streaming and direct HTTP responses.
  17. *
  18. * This is a wrapper around `WebStandardStreamableHTTPServerTransport` that provides Node.js HTTP compatibility.
  19. * It uses the `@hono/node-server` library to convert between Node.js HTTP and Web Standard APIs.
  20. *
  21. * Usage example:
  22. *
  23. * ```typescript
  24. * // Stateful mode - server sets the session ID
  25. * const statefulTransport = new StreamableHTTPServerTransport({
  26. * sessionIdGenerator: () => randomUUID(),
  27. * });
  28. *
  29. * // Stateless mode - explicitly set session ID to undefined
  30. * const statelessTransport = new StreamableHTTPServerTransport({
  31. * sessionIdGenerator: undefined,
  32. * });
  33. *
  34. * // Using with pre-parsed request body
  35. * app.post('/mcp', (req, res) => {
  36. * transport.handleRequest(req, res, req.body);
  37. * });
  38. * ```
  39. *
  40. * In stateful mode:
  41. * - Session ID is generated and included in response headers
  42. * - Session ID is always included in initialization responses
  43. * - Requests with invalid session IDs are rejected with 404 Not Found
  44. * - Non-initialization requests without a session ID are rejected with 400 Bad Request
  45. * - State is maintained in-memory (connections, message history)
  46. *
  47. * In stateless mode:
  48. * - No Session ID is included in any responses
  49. * - No session validation is performed
  50. */
  51. class StreamableHTTPServerTransport {
  52. constructor(options = {}) {
  53. // Store auth and parsedBody per request for passing through to handleRequest
  54. this._requestContext = new WeakMap();
  55. this._webStandardTransport = new webStandardStreamableHttp_js_1.WebStandardStreamableHTTPServerTransport(options);
  56. // Create a request listener that wraps the web standard transport
  57. // getRequestListener converts Node.js HTTP to Web Standard and properly handles SSE streaming
  58. // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would
  59. // break frameworks like Next.js whose response classes extend the native Response
  60. this._requestListener = (0, node_server_1.getRequestListener)(async (webRequest) => {
  61. // Get context if available (set during handleRequest)
  62. const context = this._requestContext.get(webRequest);
  63. return this._webStandardTransport.handleRequest(webRequest, {
  64. authInfo: context?.authInfo,
  65. parsedBody: context?.parsedBody
  66. });
  67. }, { overrideGlobalObjects: false });
  68. }
  69. /**
  70. * Gets the session ID for this transport instance.
  71. */
  72. get sessionId() {
  73. return this._webStandardTransport.sessionId;
  74. }
  75. /**
  76. * Sets callback for when the transport is closed.
  77. */
  78. set onclose(handler) {
  79. this._webStandardTransport.onclose = handler;
  80. }
  81. get onclose() {
  82. return this._webStandardTransport.onclose;
  83. }
  84. /**
  85. * Sets callback for transport errors.
  86. */
  87. set onerror(handler) {
  88. this._webStandardTransport.onerror = handler;
  89. }
  90. get onerror() {
  91. return this._webStandardTransport.onerror;
  92. }
  93. /**
  94. * Sets callback for incoming messages.
  95. */
  96. set onmessage(handler) {
  97. this._webStandardTransport.onmessage = handler;
  98. }
  99. get onmessage() {
  100. return this._webStandardTransport.onmessage;
  101. }
  102. /**
  103. * Starts the transport. This is required by the Transport interface but is a no-op
  104. * for the Streamable HTTP transport as connections are managed per-request.
  105. */
  106. async start() {
  107. return this._webStandardTransport.start();
  108. }
  109. /**
  110. * Closes the transport and all active connections.
  111. */
  112. async close() {
  113. return this._webStandardTransport.close();
  114. }
  115. /**
  116. * Sends a JSON-RPC message through the transport.
  117. */
  118. async send(message, options) {
  119. return this._webStandardTransport.send(message, options);
  120. }
  121. /**
  122. * Handles an incoming HTTP request, whether GET or POST.
  123. *
  124. * This method converts Node.js HTTP objects to Web Standard Request/Response
  125. * and delegates to the underlying WebStandardStreamableHTTPServerTransport.
  126. *
  127. * @param req - Node.js IncomingMessage, optionally with auth property from middleware
  128. * @param res - Node.js ServerResponse
  129. * @param parsedBody - Optional pre-parsed body from body-parser middleware
  130. */
  131. async handleRequest(req, res, parsedBody) {
  132. // Store context for this request to pass through auth and parsedBody
  133. // We need to intercept the request creation to attach this context
  134. const authInfo = req.auth;
  135. // Create a custom handler that includes our context
  136. // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would
  137. // break frameworks like Next.js whose response classes extend the native Response
  138. const handler = (0, node_server_1.getRequestListener)(async (webRequest) => {
  139. return this._webStandardTransport.handleRequest(webRequest, {
  140. authInfo,
  141. parsedBody
  142. });
  143. }, { overrideGlobalObjects: false });
  144. // Delegate to the request listener which handles all the Node.js <-> Web Standard conversion
  145. // including proper SSE streaming support
  146. await handler(req, res);
  147. }
  148. /**
  149. * Close an SSE stream for a specific request, triggering client reconnection.
  150. * Use this to implement polling behavior during long-running operations -
  151. * client will reconnect after the retry interval specified in the priming event.
  152. */
  153. closeSSEStream(requestId) {
  154. this._webStandardTransport.closeSSEStream(requestId);
  155. }
  156. /**
  157. * Close the standalone GET SSE stream, triggering client reconnection.
  158. * Use this to implement polling behavior for server-initiated notifications.
  159. */
  160. closeStandaloneSSEStream() {
  161. this._webStandardTransport.closeStandaloneSSEStream();
  162. }
  163. }
  164. exports.StreamableHTTPServerTransport = StreamableHTTPServerTransport;
  165. //# sourceMappingURL=streamableHttp.js.map