http.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. "use strict";
  2. var __create = Object.create;
  3. var __defProp = Object.defineProperty;
  4. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  5. var __getOwnPropNames = Object.getOwnPropertyNames;
  6. var __getProtoOf = Object.getPrototypeOf;
  7. var __hasOwnProp = Object.prototype.hasOwnProperty;
  8. var __export = (target, all) => {
  9. for (var name in all)
  10. __defProp(target, name, { get: all[name], enumerable: true });
  11. };
  12. var __copyProps = (to, from, except, desc) => {
  13. if (from && typeof from === "object" || typeof from === "function") {
  14. for (let key of __getOwnPropNames(from))
  15. if (!__hasOwnProp.call(to, key) && key !== except)
  16. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  17. }
  18. return to;
  19. };
  20. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  21. // If the importer is in node compatibility mode or this is not an ESM
  22. // file that has been converted to a CommonJS file using a Babel-
  23. // compatible transform (i.e. "__esModule" has not been set), then set
  24. // "default" to the CommonJS "module.exports" for node compatibility.
  25. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  26. mod
  27. ));
  28. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  29. var http_exports = {};
  30. __export(http_exports, {
  31. httpAddressToString: () => httpAddressToString,
  32. installHttpTransport: () => installHttpTransport,
  33. startHttpServer: () => startHttpServer
  34. });
  35. module.exports = __toCommonJS(http_exports);
  36. var import_assert = __toESM(require("assert"));
  37. var import_http = __toESM(require("http"));
  38. var import_crypto = __toESM(require("crypto"));
  39. var import_utilsBundle = require("playwright-core/lib/utilsBundle");
  40. var mcpBundle = __toESM(require("./bundle"));
  41. var mcpServer = __toESM(require("./server"));
  42. const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
  43. async function startHttpServer(config, abortSignal) {
  44. const { host, port } = config;
  45. const httpServer = import_http.default.createServer();
  46. decorateServer(httpServer);
  47. await new Promise((resolve, reject) => {
  48. httpServer.on("error", reject);
  49. abortSignal?.addEventListener("abort", () => {
  50. httpServer.close();
  51. reject(new Error("Aborted"));
  52. });
  53. httpServer.listen(port, host, () => {
  54. resolve();
  55. httpServer.removeListener("error", reject);
  56. });
  57. });
  58. return httpServer;
  59. }
  60. function httpAddressToString(address) {
  61. (0, import_assert.default)(address, "Could not bind server socket");
  62. if (typeof address === "string")
  63. return address;
  64. const resolvedPort = address.port;
  65. let resolvedHost = address.family === "IPv4" ? address.address : `[${address.address}]`;
  66. if (resolvedHost === "0.0.0.0" || resolvedHost === "[::]")
  67. resolvedHost = "localhost";
  68. return `http://${resolvedHost}:${resolvedPort}`;
  69. }
  70. async function installHttpTransport(httpServer, serverBackendFactory, unguessableUrl, allowedHosts) {
  71. const url = httpAddressToString(httpServer.address());
  72. const host = new URL(url).host;
  73. allowedHosts = (allowedHosts || [host]).map((h) => h.toLowerCase());
  74. const allowAnyHost = allowedHosts.includes("*");
  75. const pathPrefix = unguessableUrl ? `/${import_crypto.default.randomUUID()}` : "";
  76. const sseSessions = /* @__PURE__ */ new Map();
  77. const streamableSessions = /* @__PURE__ */ new Map();
  78. httpServer.on("request", async (req, res) => {
  79. if (!allowAnyHost) {
  80. const host2 = req.headers.host?.toLowerCase();
  81. if (!host2) {
  82. res.statusCode = 400;
  83. return res.end("Missing host");
  84. }
  85. if (!allowedHosts.includes(host2)) {
  86. res.statusCode = 403;
  87. return res.end("Access is only allowed at " + allowedHosts.join(", "));
  88. }
  89. }
  90. if (!req.url?.startsWith(pathPrefix)) {
  91. res.statusCode = 404;
  92. return res.end("Not found");
  93. }
  94. const path = req.url?.slice(pathPrefix.length);
  95. const url2 = new URL(`http://localhost${path}`);
  96. if (url2.pathname === "/killkillkill" && req.method === "GET") {
  97. res.statusCode = 200;
  98. res.end("Killing process");
  99. process.emit("SIGINT");
  100. return;
  101. }
  102. if (url2.pathname.startsWith("/sse"))
  103. await handleSSE(serverBackendFactory, req, res, url2, sseSessions);
  104. else
  105. await handleStreamable(serverBackendFactory, req, res, streamableSessions);
  106. });
  107. return `${url}${pathPrefix}`;
  108. }
  109. async function handleSSE(serverBackendFactory, req, res, url, sessions) {
  110. if (req.method === "POST") {
  111. const sessionId = url.searchParams.get("sessionId");
  112. if (!sessionId) {
  113. res.statusCode = 400;
  114. return res.end("Missing sessionId");
  115. }
  116. const transport = sessions.get(sessionId);
  117. if (!transport) {
  118. res.statusCode = 404;
  119. return res.end("Session not found");
  120. }
  121. return await transport.handlePostMessage(req, res);
  122. } else if (req.method === "GET") {
  123. const transport = new mcpBundle.SSEServerTransport("/sse", res);
  124. sessions.set(transport.sessionId, transport);
  125. testDebug(`create SSE session: ${transport.sessionId}`);
  126. await mcpServer.connect(serverBackendFactory, transport, false);
  127. res.on("close", () => {
  128. testDebug(`delete SSE session: ${transport.sessionId}`);
  129. sessions.delete(transport.sessionId);
  130. });
  131. return;
  132. }
  133. res.statusCode = 405;
  134. res.end("Method not allowed");
  135. }
  136. async function handleStreamable(serverBackendFactory, req, res, sessions) {
  137. const sessionId = req.headers["mcp-session-id"];
  138. if (sessionId) {
  139. const transport = sessions.get(sessionId);
  140. if (!transport) {
  141. res.statusCode = 404;
  142. res.end("Session not found");
  143. return;
  144. }
  145. return await transport.handleRequest(req, res);
  146. }
  147. if (req.method === "POST") {
  148. const transport = new mcpBundle.StreamableHTTPServerTransport({
  149. sessionIdGenerator: () => import_crypto.default.randomUUID(),
  150. onsessioninitialized: async (sessionId2) => {
  151. testDebug(`create http session: ${transport.sessionId}`);
  152. await mcpServer.connect(serverBackendFactory, transport, true);
  153. sessions.set(sessionId2, transport);
  154. }
  155. });
  156. transport.onclose = () => {
  157. if (!transport.sessionId)
  158. return;
  159. sessions.delete(transport.sessionId);
  160. testDebug(`delete http session: ${transport.sessionId}`);
  161. };
  162. await transport.handleRequest(req, res);
  163. return;
  164. }
  165. res.statusCode = 400;
  166. res.end("Invalid request");
  167. }
  168. function decorateServer(server) {
  169. const sockets = /* @__PURE__ */ new Set();
  170. server.on("connection", (socket) => {
  171. sockets.add(socket);
  172. socket.once("close", () => sockets.delete(socket));
  173. });
  174. const close = server.close;
  175. server.close = (callback) => {
  176. for (const socket of sockets)
  177. socket.destroy();
  178. sockets.clear();
  179. return close.call(server, callback);
  180. };
  181. }
  182. // Annotate the CommonJS export names for ESM import in node:
  183. 0 && (module.exports = {
  184. httpAddressToString,
  185. installHttpTransport,
  186. startHttpServer
  187. });