@productbrain/mcp 0.0.1-beta.15 → 0.0.1-beta.151

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/dist/http.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import {
2
2
  SERVER_VERSION,
3
- createProductBrainServer
4
- } from "./chunk-3QNBVXRP.js";
5
- import {
6
3
  bootstrapHttp,
4
+ createProductBrainServer,
5
+ hashKey,
6
+ initFeatureFlags,
7
7
  runWithAuth
8
- } from "./chunk-47LO6K2R.js";
8
+ } from "./chunk-ML7BPLBX.js";
9
9
  import {
10
+ getPostHogClient,
10
11
  initAnalytics,
11
12
  shutdownAnalytics
12
- } from "./chunk-XBMI6QHR.js";
13
+ } from "./chunk-X3S5UTTZ.js";
13
14
 
14
15
  // src/http.ts
15
16
  import { createHash, randomUUID } from "crypto";
@@ -19,19 +20,21 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
19
20
  import rateLimit from "express-rate-limit";
20
21
  bootstrapHttp();
21
22
  initAnalytics();
22
- var PORT = parseInt(process.env.PORT ?? process.env.MCP_PORT ?? "3000", 10);
23
+ initFeatureFlags(getPostHogClient());
24
+ var PORT = parseInt(process.env.PORT ?? process.env.MCP_PORT ?? "3002", 10);
23
25
  function baseUrl(req) {
24
26
  const proto = req.headers["x-forwarded-proto"] ?? req.protocol ?? "http";
25
27
  const host = req.headers.host ?? `localhost:${PORT}`;
26
28
  return `${proto}://${host}`;
27
29
  }
28
30
  var app = express();
31
+ app.set("trust proxy", 1);
29
32
  app.use(express.json());
30
33
  var ALLOWED_ORIGINS = process.env.CORS_ORIGINS?.split(",").map((o) => o.trim()).filter(Boolean);
31
34
  app.use((_req, res, next) => {
32
35
  const origin = _req.headers.origin;
33
- if (!ALLOWED_ORIGINS || origin && ALLOWED_ORIGINS.includes(origin)) {
34
- res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
36
+ if (ALLOWED_ORIGINS && origin && ALLOWED_ORIGINS.includes(origin)) {
37
+ res.setHeader("Access-Control-Allow-Origin", origin);
35
38
  }
36
39
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
37
40
  res.setHeader(
@@ -62,17 +65,33 @@ app.get("/.well-known/oauth-authorization-server", (req, res) => {
62
65
  token_endpoint: `${base}/oauth/token`,
63
66
  registration_endpoint: `${base}/register`,
64
67
  response_types_supported: ["code"],
65
- grant_types_supported: ["authorization_code"],
68
+ grant_types_supported: ["authorization_code", "refresh_token"],
66
69
  code_challenge_methods_supported: ["S256"],
67
70
  token_endpoint_auth_methods_supported: ["none"],
68
71
  scopes_supported: ["mcp:tools", "mcp:resources"]
69
72
  });
70
73
  });
74
+ var authLimiter = rateLimit({
75
+ windowMs: 6e4,
76
+ max: 20,
77
+ standardHeaders: true,
78
+ legacyHeaders: false,
79
+ message: { error: "Too many auth requests. Try again later." }
80
+ });
71
81
  var registeredClients = /* @__PURE__ */ new Map();
82
+ var MAX_REGISTERED_CLIENTS = 500;
72
83
  app.post(
73
84
  "/register",
85
+ authLimiter,
74
86
  express.json(),
75
87
  (req, res) => {
88
+ if (registeredClients.size >= MAX_REGISTERED_CLIENTS) {
89
+ res.status(503).json({
90
+ error: "server_error",
91
+ error_description: "Registration limit reached. Try again later."
92
+ });
93
+ return;
94
+ }
76
95
  const { redirect_uris, client_name } = req.body;
77
96
  if (!Array.isArray(redirect_uris) || redirect_uris.length === 0) {
78
97
  res.status(400).json({
@@ -100,6 +119,12 @@ app.post(
100
119
  }
101
120
  );
102
121
  var pendingCodes = /* @__PURE__ */ new Map();
122
+ var ACCESS_TOKEN_TTL = 3600;
123
+ var ACCESS_TOKEN_TTL_MS = ACCESS_TOKEN_TTL * 1e3;
124
+ var REFRESH_TOKEN_TTL_MS = 90 * 24 * 60 * 6e4;
125
+ var refreshTokens = /* @__PURE__ */ new Map();
126
+ var accessTokens = /* @__PURE__ */ new Map();
127
+ var MAX_ACCESS_TOKENS = 1e3;
103
128
  setInterval(() => {
104
129
  const now = Date.now();
105
130
  for (const [code, auth] of pendingCodes) {
@@ -108,6 +133,23 @@ setInterval(() => {
108
133
  for (const [id, client] of registeredClients) {
109
134
  if (now - client.registeredAt > 24 * 60 * 6e4) registeredClients.delete(id);
110
135
  }
136
+ for (const [token, entry] of refreshTokens) {
137
+ if (now - entry.createdAt > REFRESH_TOKEN_TTL_MS) refreshTokens.delete(token);
138
+ }
139
+ for (const [token, entry] of accessTokens) {
140
+ if (now - entry.createdAt > ACCESS_TOKEN_TTL_MS) accessTokens.delete(token);
141
+ }
142
+ for (const [ip, rec] of authFailures) {
143
+ if (rec.blockedUntil < now && rec.firstFailure + AUTH_FAILURE_WINDOW_MS < now) {
144
+ authFailures.delete(ip);
145
+ }
146
+ }
147
+ if (authFailures.size > MAX_AUTH_FAILURE_ENTRIES) {
148
+ const sorted = [...authFailures.entries()].sort((a, b) => a[1].firstFailure - b[1].firstFailure);
149
+ for (let i = 0; i < sorted.length - MAX_AUTH_FAILURE_ENTRIES; i++) {
150
+ authFailures.delete(sorted[i][0]);
151
+ }
152
+ }
111
153
  }, 6e4);
112
154
  function esc(s) {
113
155
  return String(s ?? "").replace(
@@ -115,8 +157,8 @@ function esc(s) {
115
157
  (c) => ({ "&": "&amp;", '"': "&quot;", "<": "&lt;", ">": "&gt;" })[c]
116
158
  );
117
159
  }
118
- app.get("/authorize", (req, res) => {
119
- const { redirect_uri, code_challenge, code_challenge_method, state } = req.query;
160
+ app.get("/authorize", authLimiter, (req, res) => {
161
+ const { redirect_uri, code_challenge, code_challenge_method, state, client_id } = req.query;
120
162
  res.type("html").send(`<!DOCTYPE html>
121
163
  <html lang="en"><head>
122
164
  <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
@@ -145,6 +187,7 @@ button:hover{background:#6d28d9}
145
187
  <input type="hidden" name="code_challenge" value="${esc(code_challenge)}">
146
188
  <input type="hidden" name="code_challenge_method" value="${esc(code_challenge_method)}">
147
189
  <input type="hidden" name="state" value="${esc(state)}">
190
+ <input type="hidden" name="client_id" value="${esc(client_id)}">
148
191
  <label for="k">API Key</label>
149
192
  <input type="password" id="k" name="api_key" placeholder="pb_sk_\u2026" required autofocus>
150
193
  <p class="err" id="e">Key must start with pb_sk_</p>
@@ -158,13 +201,29 @@ e.preventDefault();document.getElementById("e").style.display="block"}}</script>
158
201
  });
159
202
  app.post(
160
203
  "/authorize",
204
+ authLimiter,
161
205
  express.urlencoded({ extended: false }),
162
206
  (req, res) => {
163
- const { api_key, redirect_uri, code_challenge, state } = req.body;
207
+ const { api_key, redirect_uri, code_challenge, state, client_id } = req.body;
164
208
  if (!api_key?.startsWith("pb_sk_")) {
165
209
  res.status(400).send("Invalid API key");
166
210
  return;
167
211
  }
212
+ if (!client_id || !registeredClients.has(client_id)) {
213
+ res.status(400).json({
214
+ error: "invalid_request",
215
+ error_description: "Unknown or missing client_id"
216
+ });
217
+ return;
218
+ }
219
+ const client = registeredClients.get(client_id);
220
+ if (!client.redirect_uris.includes(redirect_uri)) {
221
+ res.status(400).json({
222
+ error: "invalid_request",
223
+ error_description: "redirect_uri does not match any registered redirect for this client"
224
+ });
225
+ return;
226
+ }
168
227
  const code = randomUUID();
169
228
  pendingCodes.set(code, {
170
229
  apiKey: api_key,
@@ -178,12 +237,53 @@ app.post(
178
237
  res.redirect(302, url.toString());
179
238
  }
180
239
  );
240
+ function issueTokens(apiKey) {
241
+ const opaqueToken = `pb_at_${randomUUID()}`;
242
+ const now = Date.now();
243
+ if (accessTokens.size >= MAX_ACCESS_TOKENS) {
244
+ let oldestKey = "";
245
+ let oldestTime = Infinity;
246
+ for (const [k, v] of accessTokens) {
247
+ if (v.createdAt < oldestTime) {
248
+ oldestTime = v.createdAt;
249
+ oldestKey = k;
250
+ }
251
+ }
252
+ if (oldestKey) accessTokens.delete(oldestKey);
253
+ }
254
+ accessTokens.set(opaqueToken, { apiKey, createdAt: now });
255
+ const refreshToken = `pb_rt_${randomUUID()}`;
256
+ refreshTokens.set(refreshToken, { apiKey, createdAt: now });
257
+ return {
258
+ access_token: opaqueToken,
259
+ token_type: "Bearer",
260
+ expires_in: ACCESS_TOKEN_TTL,
261
+ refresh_token: refreshToken
262
+ };
263
+ }
181
264
  app.post(
182
265
  "/oauth/token",
266
+ authLimiter,
183
267
  express.urlencoded({ extended: false }),
184
268
  express.json(),
185
269
  (req, res) => {
186
- const { grant_type, code, code_verifier, redirect_uri } = req.body;
270
+ const { grant_type, code, code_verifier, redirect_uri, refresh_token } = req.body;
271
+ if (grant_type === "refresh_token") {
272
+ const entry = refreshTokens.get(refresh_token);
273
+ if (!entry) {
274
+ res.status(400).json({ error: "invalid_grant", error_description: "Invalid refresh token" });
275
+ return;
276
+ }
277
+ if (Date.now() - entry.createdAt > REFRESH_TOKEN_TTL_MS) {
278
+ refreshTokens.delete(refresh_token);
279
+ res.status(400).json({ error: "invalid_grant", error_description: "Refresh token expired" });
280
+ return;
281
+ }
282
+ const apiKey = entry.apiKey;
283
+ refreshTokens.delete(refresh_token);
284
+ res.json(issueTokens(apiKey));
285
+ return;
286
+ }
187
287
  if (grant_type !== "authorization_code") {
188
288
  res.status(400).json({ error: "unsupported_grant_type" });
189
289
  return;
@@ -203,11 +303,7 @@ app.post(
203
303
  return;
204
304
  }
205
305
  pendingCodes.delete(code);
206
- res.json({
207
- access_token: pending.apiKey,
208
- token_type: "Bearer",
209
- expires_in: 3600
210
- });
306
+ res.json(issueTokens(pending.apiKey));
211
307
  }
212
308
  );
213
309
  var mcpLimiter = rateLimit({
@@ -217,16 +313,46 @@ var mcpLimiter = rateLimit({
217
313
  legacyHeaders: false,
218
314
  message: { error: "Too many requests. Try again later." }
219
315
  });
316
+ var authFailures = /* @__PURE__ */ new Map();
317
+ var AUTH_FAILURE_MAX = 10;
318
+ var AUTH_FAILURE_WINDOW_MS = 5 * 6e4;
319
+ var AUTH_BLOCK_DURATION_MS = 15 * 6e4;
320
+ var MAX_AUTH_FAILURE_ENTRIES = 1e4;
321
+ function checkAuthBlock(ip) {
322
+ const rec = authFailures.get(ip);
323
+ if (!rec) return false;
324
+ return rec.blockedUntil > Date.now();
325
+ }
326
+ function recordAuthFailure(ip) {
327
+ const now = Date.now();
328
+ const rec = authFailures.get(ip);
329
+ if (!rec) {
330
+ authFailures.set(ip, { count: 1, firstFailure: now, blockedUntil: 0 });
331
+ return;
332
+ }
333
+ if (now - rec.firstFailure > AUTH_FAILURE_WINDOW_MS) {
334
+ rec.count = 1;
335
+ rec.firstFailure = now;
336
+ rec.blockedUntil = 0;
337
+ } else {
338
+ rec.count++;
339
+ if (rec.count >= AUTH_FAILURE_MAX) {
340
+ rec.blockedUntil = now + AUTH_BLOCK_DURATION_MS;
341
+ }
342
+ }
343
+ }
220
344
  app.get("/health", (_req, res) => {
221
345
  res.json({ status: "ok", version: SERVER_VERSION, transport: "http" });
222
346
  });
223
347
  var sessions = /* @__PURE__ */ new Map();
224
348
  var SESSION_TTL_MS = 30 * 60 * 1e3;
225
349
  var MAX_SESSIONS = 200;
350
+ var MAX_SESSIONS_PER_KEY = 5;
226
351
  function evictStaleSessions() {
227
352
  const now = Date.now();
228
353
  for (const [id, entry] of sessions) {
229
354
  if (now - entry.lastAccess > SESSION_TTL_MS) {
355
+ logSessionLifecycle("session_deleted", id, "ttl");
230
356
  entry.transport.close().catch(() => {
231
357
  });
232
358
  sessions.delete(id);
@@ -237,6 +363,7 @@ function evictStaleSessions() {
237
363
  (a, b) => a[1].lastAccess - b[1].lastAccess
238
364
  );
239
365
  for (let i = 0; i < sorted.length - MAX_SESSIONS; i++) {
366
+ logSessionLifecycle("session_deleted", sorted[i][0], "eviction");
240
367
  sorted[i][1].transport.close().catch(() => {
241
368
  });
242
369
  sessions.delete(sorted[i][0]);
@@ -248,7 +375,20 @@ function extractBearerKey(req) {
248
375
  const header = req.headers?.authorization;
249
376
  if (typeof header !== "string" || !header.startsWith("Bearer ")) return null;
250
377
  const token = header.slice(7).trim();
251
- return token.startsWith("pb_sk_") ? token : null;
378
+ if (token.startsWith("pb_sk_")) {
379
+ return token;
380
+ }
381
+ if (token.startsWith("pb_at_")) {
382
+ const entry = accessTokens.get(token);
383
+ if (!entry) return null;
384
+ const now = Date.now();
385
+ if (now - entry.createdAt > ACCESS_TOKEN_TTL_MS) {
386
+ accessTokens.delete(token);
387
+ return null;
388
+ }
389
+ return entry.apiKey;
390
+ }
391
+ return null;
252
392
  }
253
393
  function send401(req, res) {
254
394
  const base = baseUrl(req);
@@ -257,43 +397,86 @@ function send401(req, res) {
257
397
  `Bearer resource_metadata="${base}/.well-known/oauth-protected-resource"`
258
398
  ).json({ error: "unauthorized" });
259
399
  }
260
- function logRequest(method, outcome, sessionId) {
400
+ function logRequest(method, outcome, sessionId, durationMs) {
261
401
  const ts = (/* @__PURE__ */ new Date()).toISOString();
262
402
  const sid = sessionId ? ` session=${sessionId}` : "";
263
- process.stderr.write(`[HTTP] ${ts} ${method} ${outcome}${sid}
403
+ const dur = durationMs != null ? ` duration=${durationMs}ms` : "";
404
+ process.stderr.write(`[HTTP] ${ts} ${method} ${outcome}${sid}${dur}
405
+ `);
406
+ }
407
+ function logSessionLifecycle(event, sessionId, reason) {
408
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
409
+ const r = reason ? ` reason=${reason}` : "";
410
+ process.stderr.write(`[HTTP] ${ts} ${event} session=${sessionId}${r}
264
411
  `);
265
412
  }
266
413
  app.post("/mcp", mcpLimiter, async (req, res) => {
414
+ const reqIp = req.ip ?? "unknown";
415
+ if (checkAuthBlock(reqIp)) {
416
+ res.status(429).json({ error: "Too many failed auth attempts. Try again later." });
417
+ return;
418
+ }
267
419
  const apiKey = extractBearerKey(req);
268
420
  if (!apiKey) {
269
421
  logRequest("POST", "auth_fail");
422
+ recordAuthFailure(reqIp);
270
423
  send401(req, res);
271
424
  return;
272
425
  }
273
426
  const sessionId = req.headers["mcp-session-id"];
427
+ const reqStart = Date.now();
274
428
  try {
275
429
  await runWithAuth({ apiKey }, async () => {
276
430
  if (sessionId && sessions.has(sessionId)) {
277
431
  const entry = sessions.get(sessionId);
432
+ if (entry.keyHash !== hashKey(apiKey)) {
433
+ res.status(403).json({
434
+ jsonrpc: "2.0",
435
+ error: { code: -32e3, message: "Session key mismatch" },
436
+ id: null
437
+ });
438
+ return;
439
+ }
278
440
  entry.lastAccess = Date.now();
279
441
  await entry.transport.handleRequest(req, res, req.body);
280
- logRequest("POST", "ok", sessionId);
442
+ logRequest("POST", "ok", sessionId, Date.now() - reqStart);
281
443
  } else if (!sessionId && isInitializeRequest(req.body)) {
444
+ const keyH = hashKey(apiKey);
445
+ let keySessionCount = 0;
446
+ for (const entry of sessions.values()) {
447
+ if (entry.keyHash === keyH) keySessionCount++;
448
+ }
449
+ if (keySessionCount >= MAX_SESSIONS_PER_KEY) {
450
+ res.status(429).json({
451
+ jsonrpc: "2.0",
452
+ error: { code: -32e3, message: "Too many sessions for this API key" },
453
+ id: null
454
+ });
455
+ return;
456
+ }
282
457
  const transport = new StreamableHTTPServerTransport({
283
458
  sessionIdGenerator: () => randomUUID(),
284
459
  onsessioninitialized: (sid) => {
285
- sessions.set(sid, { transport, lastAccess: Date.now() });
286
- logRequest("POST", "ok", sid);
460
+ sessions.set(sid, { transport, lastAccess: Date.now(), keyHash: keyH });
461
+ logSessionLifecycle("session_created", sid);
287
462
  }
288
463
  });
289
464
  transport.onclose = () => {
290
465
  const sid = transport.sessionId;
291
- if (sid) sessions.delete(sid);
466
+ if (sid) {
467
+ logSessionLifecycle("session_deleted", sid, "onclose");
468
+ sessions.delete(sid);
469
+ }
292
470
  };
293
471
  const server = createProductBrainServer();
294
472
  await server.connect(transport);
295
473
  await transport.handleRequest(req, res, req.body);
474
+ logRequest("POST", "ok", transport.sessionId ?? void 0, Date.now() - reqStart);
296
475
  } else {
476
+ process.stderr.write(
477
+ `[HTTP] ${(/* @__PURE__ */ new Date()).toISOString()} session_invalid no valid session ID (client may have omitted Mcp-Session-Id)
478
+ `
479
+ );
297
480
  res.status(400).json({
298
481
  jsonrpc: "2.0",
299
482
  error: { code: -32e3, message: "Bad Request: no valid session ID provided" },
@@ -302,7 +485,7 @@ app.post("/mcp", mcpLimiter, async (req, res) => {
302
485
  }
303
486
  });
304
487
  } catch (err) {
305
- logRequest("POST", "error", sessionId);
488
+ logRequest("POST", "error", sessionId, Date.now() - reqStart);
306
489
  if (!res.headersSent) {
307
490
  res.status(500).json({
308
491
  jsonrpc: "2.0",
@@ -313,9 +496,15 @@ app.post("/mcp", mcpLimiter, async (req, res) => {
313
496
  }
314
497
  });
315
498
  app.get("/mcp", mcpLimiter, async (req, res) => {
499
+ const reqIp = req.ip ?? "unknown";
500
+ if (checkAuthBlock(reqIp)) {
501
+ res.status(429).json({ error: "Too many failed auth attempts. Try again later." });
502
+ return;
503
+ }
316
504
  const apiKey = extractBearerKey(req);
317
505
  if (!apiKey) {
318
506
  logRequest("GET", "auth_fail");
507
+ recordAuthFailure(reqIp);
319
508
  send401(req, res);
320
509
  return;
321
510
  }
@@ -327,6 +516,14 @@ app.get("/mcp", mcpLimiter, async (req, res) => {
327
516
  try {
328
517
  await runWithAuth({ apiKey }, async () => {
329
518
  const entry = sessions.get(sessionId);
519
+ if (entry.keyHash !== hashKey(apiKey)) {
520
+ res.status(403).json({
521
+ jsonrpc: "2.0",
522
+ error: { code: -32e3, message: "Session key mismatch" },
523
+ id: null
524
+ });
525
+ return;
526
+ }
330
527
  entry.lastAccess = Date.now();
331
528
  await entry.transport.handleRequest(req, res);
332
529
  logRequest("GET", "ok", sessionId);
@@ -336,9 +533,15 @@ app.get("/mcp", mcpLimiter, async (req, res) => {
336
533
  }
337
534
  });
338
535
  app.delete("/mcp", mcpLimiter, async (req, res) => {
536
+ const reqIp = req.ip ?? "unknown";
537
+ if (checkAuthBlock(reqIp)) {
538
+ res.status(429).json({ error: "Too many failed auth attempts. Try again later." });
539
+ return;
540
+ }
339
541
  const apiKey = extractBearerKey(req);
340
542
  if (!apiKey) {
341
543
  logRequest("DELETE", "auth_fail");
544
+ recordAuthFailure(reqIp);
342
545
  send401(req, res);
343
546
  return;
344
547
  }
@@ -350,6 +553,14 @@ app.delete("/mcp", mcpLimiter, async (req, res) => {
350
553
  try {
351
554
  await runWithAuth({ apiKey }, async () => {
352
555
  const entry = sessions.get(sessionId);
556
+ if (entry.keyHash !== hashKey(apiKey)) {
557
+ res.status(403).json({
558
+ jsonrpc: "2.0",
559
+ error: { code: -32e3, message: "Session key mismatch" },
560
+ id: null
561
+ });
562
+ return;
563
+ }
353
564
  await entry.transport.handleRequest(req, res);
354
565
  logRequest("DELETE", "ok", sessionId);
355
566
  });
@@ -357,18 +568,40 @@ app.delete("/mcp", mcpLimiter, async (req, res) => {
357
568
  logRequest("DELETE", "error", sessionId);
358
569
  }
359
570
  });
360
- app.listen(PORT, "0.0.0.0", () => {
361
- console.log(`Product Brain MCP HTTP server v${SERVER_VERSION} listening on port ${PORT}`);
571
+ process.on("unhandledRejection", (reason) => {
572
+ const msg = reason instanceof Error ? reason.message : String(reason);
573
+ console.error(`[MCP HTTP] Unhandled rejection: ${msg}`);
574
+ });
575
+ process.on("uncaughtException", (err) => {
576
+ console.error(`[MCP HTTP] Uncaught exception: ${err.stack ?? err.message}`);
577
+ gracefulShutdown();
362
578
  });
579
+ var shuttingDown = false;
363
580
  async function gracefulShutdown() {
581
+ if (shuttingDown) return;
582
+ shuttingDown = true;
583
+ setTimeout(() => process.exit(1), 3e3).unref();
364
584
  console.log("Shutting down...");
365
585
  for (const [, entry] of sessions) {
366
586
  await entry.transport.close().catch(() => {
367
587
  });
368
588
  }
369
- await shutdownAnalytics();
589
+ try {
590
+ await shutdownAnalytics();
591
+ } catch {
592
+ }
370
593
  process.exit(0);
371
594
  }
595
+ var LISTEN_HOST = "0.0.0.0";
596
+ var httpServer = app.listen(PORT, LISTEN_HOST, () => {
597
+ console.log(
598
+ `Product Brain MCP HTTP server v${SERVER_VERSION} listening on ${LISTEN_HOST}:${PORT}`
599
+ );
600
+ });
601
+ httpServer.on("error", (err) => {
602
+ console.error(`[MCP HTTP] Server error: ${err.message}`);
603
+ process.exit(1);
604
+ });
372
605
  process.on("SIGINT", gracefulShutdown);
373
606
  process.on("SIGTERM", gracefulShutdown);
374
607
  //# sourceMappingURL=http.js.map