mcp-proxy 6.5.2 → 6.5.4
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/LICENSE +21 -20
- package/README.md +2 -0
- package/dist/bin/mcp-proxy.mjs +7 -1
- package/dist/bin/mcp-proxy.mjs.map +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -1
- package/dist/{stdio-DLwYts_5.mjs → stdio-BQunCFor.mjs} +76 -2
- package/dist/stdio-BQunCFor.mjs.map +1 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/bin/mcp-proxy.ts +7 -0
- package/src/startHTTPServer.test.ts +197 -0
- package/src/startHTTPServer.ts +115 -6
- package/dist/stdio-DLwYts_5.mjs.map +0 -1
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/bin/mcp-proxy.ts
CHANGED
|
@@ -80,6 +80,12 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
80
80
|
describe: "The host to listen on",
|
|
81
81
|
type: "string",
|
|
82
82
|
},
|
|
83
|
+
keepAliveTimeout: {
|
|
84
|
+
default: 300000,
|
|
85
|
+
describe:
|
|
86
|
+
"The HTTP keep-alive timeout in milliseconds for stateful stream sessions (default: 5 minutes)",
|
|
87
|
+
type: "number",
|
|
88
|
+
},
|
|
83
89
|
port: {
|
|
84
90
|
default: 8080,
|
|
85
91
|
describe: "The port to listen on",
|
|
@@ -244,6 +250,7 @@ const proxy = async () => {
|
|
|
244
250
|
createServer,
|
|
245
251
|
eventStore: new InMemoryEventStore(),
|
|
246
252
|
host: argv.host,
|
|
253
|
+
keepAliveTimeout: argv.keepAliveTimeout,
|
|
247
254
|
port: argv.port,
|
|
248
255
|
sseEndpoint:
|
|
249
256
|
argv.server && argv.server !== "sse"
|
|
@@ -131,6 +131,73 @@ it("proxies messages between HTTP stream and stdio servers", async () => {
|
|
|
131
131
|
expect(onClose).toHaveBeenCalled();
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
it(
|
|
135
|
+
"keeps stateful HTTP stream sessions alive after idle keep-alive timeout window",
|
|
136
|
+
async () => {
|
|
137
|
+
const port = await getRandomPort();
|
|
138
|
+
const onClose = vi.fn().mockResolvedValue(undefined);
|
|
139
|
+
|
|
140
|
+
const httpServer = await startHTTPServer({
|
|
141
|
+
createServer: async () => {
|
|
142
|
+
return new Server(
|
|
143
|
+
{ name: "test", version: "1.0.0" },
|
|
144
|
+
{ capabilities: {} },
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
onClose,
|
|
148
|
+
port,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const initializeResponse = await fetch(`http://localhost:${port}/mcp`, {
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
id: 1,
|
|
154
|
+
jsonrpc: "2.0",
|
|
155
|
+
method: "initialize",
|
|
156
|
+
params: {
|
|
157
|
+
capabilities: {},
|
|
158
|
+
clientInfo: { name: "test", version: "1.0.0" },
|
|
159
|
+
protocolVersion: "2025-03-26",
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
headers: {
|
|
163
|
+
Accept: "application/json, text/event-stream",
|
|
164
|
+
"Content-Type": "application/json",
|
|
165
|
+
},
|
|
166
|
+
method: "POST",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(initializeResponse.status).toBe(200);
|
|
170
|
+
const sessionId = initializeResponse.headers.get("mcp-session-id");
|
|
171
|
+
expect(sessionId).toBeTruthy();
|
|
172
|
+
await initializeResponse.text();
|
|
173
|
+
|
|
174
|
+
await delay(6_000);
|
|
175
|
+
|
|
176
|
+
const listToolsResponse = await fetch(`http://localhost:${port}/mcp`, {
|
|
177
|
+
body: JSON.stringify({
|
|
178
|
+
id: 2,
|
|
179
|
+
jsonrpc: "2.0",
|
|
180
|
+
method: "tools/list",
|
|
181
|
+
params: {},
|
|
182
|
+
}),
|
|
183
|
+
headers: {
|
|
184
|
+
Accept: "application/json, text/event-stream",
|
|
185
|
+
"Content-Type": "application/json",
|
|
186
|
+
"mcp-session-id": sessionId!,
|
|
187
|
+
},
|
|
188
|
+
method: "POST",
|
|
189
|
+
});
|
|
190
|
+
const listToolsBody = await listToolsResponse.text();
|
|
191
|
+
|
|
192
|
+
expect(listToolsResponse.status).not.toBe(404);
|
|
193
|
+
expect(listToolsBody).not.toContain("Session not found");
|
|
194
|
+
expect(onClose).not.toHaveBeenCalled();
|
|
195
|
+
|
|
196
|
+
await httpServer.close();
|
|
197
|
+
},
|
|
198
|
+
15_000,
|
|
199
|
+
);
|
|
200
|
+
|
|
134
201
|
it("proxies messages between SSE and stdio servers", async () => {
|
|
135
202
|
const stdioTransport = new StdioClientTransport({
|
|
136
203
|
args: ["src/fixtures/simple-stdio-server.ts"],
|
|
@@ -926,6 +993,136 @@ it("accepts requests with valid Bearer token in stateless mode", async () => {
|
|
|
926
993
|
await stdioClient.close();
|
|
927
994
|
});
|
|
928
995
|
|
|
996
|
+
it("returns 401 for authenticated stream requests without a session ID", async () => {
|
|
997
|
+
const port = await getRandomPort();
|
|
998
|
+
const authenticate = vi.fn().mockResolvedValue({ userId: "test-user" });
|
|
999
|
+
const createServer = vi.fn(async () => {
|
|
1000
|
+
return new Server({ name: "test", version: "1.0.0" }, { capabilities: {} });
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
const httpServer = await startHTTPServer({
|
|
1004
|
+
authenticate,
|
|
1005
|
+
createServer,
|
|
1006
|
+
port,
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
try {
|
|
1010
|
+
const response = await fetch(`http://localhost:${port}/mcp`, {
|
|
1011
|
+
body: JSON.stringify({
|
|
1012
|
+
id: 1,
|
|
1013
|
+
jsonrpc: "2.0",
|
|
1014
|
+
method: "tools/list",
|
|
1015
|
+
}),
|
|
1016
|
+
headers: {
|
|
1017
|
+
Accept: "application/json, text/event-stream",
|
|
1018
|
+
Authorization: "Bearer valid-token",
|
|
1019
|
+
"Content-Type": "application/json",
|
|
1020
|
+
},
|
|
1021
|
+
method: "POST",
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
expect(response.status).toBe(401);
|
|
1025
|
+
|
|
1026
|
+
const errorResponse = (await response.json()) as {
|
|
1027
|
+
error: { code: number; message: string };
|
|
1028
|
+
id: null | number;
|
|
1029
|
+
jsonrpc: string;
|
|
1030
|
+
};
|
|
1031
|
+
expect(errorResponse.error).toEqual({
|
|
1032
|
+
code: -32000,
|
|
1033
|
+
message: "Unauthorized: No valid session ID provided",
|
|
1034
|
+
});
|
|
1035
|
+
expect(errorResponse.id).toBe(1);
|
|
1036
|
+
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
1037
|
+
expect(createServer).not.toHaveBeenCalled();
|
|
1038
|
+
} finally {
|
|
1039
|
+
await httpServer.close();
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
it("returns 401 for authenticated stream GET requests without a session ID", async () => {
|
|
1044
|
+
const port = await getRandomPort();
|
|
1045
|
+
const authenticate = vi.fn().mockResolvedValue({ userId: "test-user" });
|
|
1046
|
+
|
|
1047
|
+
const httpServer = await startHTTPServer({
|
|
1048
|
+
authenticate,
|
|
1049
|
+
createServer: async () => {
|
|
1050
|
+
return new Server(
|
|
1051
|
+
{ name: "test", version: "1.0.0" },
|
|
1052
|
+
{ capabilities: {} },
|
|
1053
|
+
);
|
|
1054
|
+
},
|
|
1055
|
+
port,
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
try {
|
|
1059
|
+
const response = await fetch(`http://localhost:${port}/mcp`, {
|
|
1060
|
+
headers: {
|
|
1061
|
+
Accept: "text/event-stream",
|
|
1062
|
+
Authorization: "Bearer valid-token",
|
|
1063
|
+
},
|
|
1064
|
+
method: "GET",
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
expect(response.status).toBe(401);
|
|
1068
|
+
|
|
1069
|
+
const errorResponse = (await response.json()) as {
|
|
1070
|
+
error: { code: number; message: string };
|
|
1071
|
+
id: null | number;
|
|
1072
|
+
jsonrpc: string;
|
|
1073
|
+
};
|
|
1074
|
+
expect(errorResponse.error.message).toBe(
|
|
1075
|
+
"Unauthorized: No valid session ID provided",
|
|
1076
|
+
);
|
|
1077
|
+
expect(errorResponse.id).toBeNull();
|
|
1078
|
+
expect(authenticate).not.toHaveBeenCalled();
|
|
1079
|
+
} finally {
|
|
1080
|
+
await httpServer.close();
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
it("keeps malformed authenticated stream requests as 400", async () => {
|
|
1085
|
+
const port = await getRandomPort();
|
|
1086
|
+
const authenticate = vi.fn().mockResolvedValue({ userId: "test-user" });
|
|
1087
|
+
|
|
1088
|
+
const httpServer = await startHTTPServer({
|
|
1089
|
+
authenticate,
|
|
1090
|
+
createServer: async () => {
|
|
1091
|
+
return new Server(
|
|
1092
|
+
{ name: "test", version: "1.0.0" },
|
|
1093
|
+
{ capabilities: {} },
|
|
1094
|
+
);
|
|
1095
|
+
},
|
|
1096
|
+
port,
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
try {
|
|
1100
|
+
const response = await fetch(`http://localhost:${port}/mcp`, {
|
|
1101
|
+
body: JSON.stringify({ malformed: true }),
|
|
1102
|
+
headers: {
|
|
1103
|
+
Accept: "application/json, text/event-stream",
|
|
1104
|
+
Authorization: "Bearer valid-token",
|
|
1105
|
+
"Content-Type": "application/json",
|
|
1106
|
+
},
|
|
1107
|
+
method: "POST",
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
expect(response.status).toBe(400);
|
|
1111
|
+
|
|
1112
|
+
const errorResponse = (await response.json()) as {
|
|
1113
|
+
error: { code: number; message: string };
|
|
1114
|
+
id: null | number;
|
|
1115
|
+
jsonrpc: string;
|
|
1116
|
+
};
|
|
1117
|
+
expect(errorResponse.error.message).toBe(
|
|
1118
|
+
"Bad Request: No valid session ID provided",
|
|
1119
|
+
);
|
|
1120
|
+
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
1121
|
+
} finally {
|
|
1122
|
+
await httpServer.close();
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
|
|
929
1126
|
it("returns 401 when authenticate callback returns null in stateless mode", async () => {
|
|
930
1127
|
const stdioTransport = new StdioClientTransport({
|
|
931
1128
|
args: ["src/fixtures/simple-stdio-server.ts"],
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -4,7 +4,10 @@ import {
|
|
|
4
4
|
EventStore,
|
|
5
5
|
StreamableHTTPServerTransport,
|
|
6
6
|
} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
isInitializeRequest,
|
|
9
|
+
JSONRPCMessageSchema,
|
|
10
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
11
|
import fs from "fs";
|
|
9
12
|
import http from "http";
|
|
10
13
|
import https from "https";
|
|
@@ -13,6 +16,8 @@ import { randomUUID } from "node:crypto";
|
|
|
13
16
|
import { AuthConfig, AuthenticationMiddleware } from "./authentication.js";
|
|
14
17
|
import { InMemoryEventStore } from "./InMemoryEventStore.js";
|
|
15
18
|
|
|
19
|
+
const DEFAULT_KEEP_ALIVE_TIMEOUT = 300_000;
|
|
20
|
+
|
|
16
21
|
export interface CorsOptions {
|
|
17
22
|
allowedHeaders?: string | string[]; // Allow string[] or '*' for wildcard
|
|
18
23
|
credentials?: boolean;
|
|
@@ -60,6 +65,35 @@ const createJsonRpcErrorResponse = (code: number, message: string) => {
|
|
|
60
65
|
});
|
|
61
66
|
};
|
|
62
67
|
|
|
68
|
+
type SessionUnauthorizedResponseOptions = {
|
|
69
|
+
readonly body?: unknown;
|
|
70
|
+
readonly oauth?: AuthConfig["oauth"];
|
|
71
|
+
readonly res: http.ServerResponse;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const getRequestId = (body: unknown): unknown => {
|
|
75
|
+
if (
|
|
76
|
+
typeof body !== "object" ||
|
|
77
|
+
body === null ||
|
|
78
|
+
Array.isArray(body) ||
|
|
79
|
+
!("id" in body)
|
|
80
|
+
) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return body.id;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const isJsonRpcMessage = (message: unknown): boolean => {
|
|
88
|
+
return JSONRPCMessageSchema.safeParse(message).success;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const isJsonRpcBody = (body: unknown): boolean => {
|
|
92
|
+
return Array.isArray(body)
|
|
93
|
+
? body.every(isJsonRpcMessage)
|
|
94
|
+
: isJsonRpcMessage(body);
|
|
95
|
+
};
|
|
96
|
+
|
|
63
97
|
// Helper function to get WWW-Authenticate header value
|
|
64
98
|
const getWWWAuthenticateHeader = (
|
|
65
99
|
oauth?: AuthConfig["oauth"],
|
|
@@ -123,6 +157,35 @@ const getWWWAuthenticateHeader = (
|
|
|
123
157
|
return `Bearer ${params.join(", ")}`;
|
|
124
158
|
};
|
|
125
159
|
|
|
160
|
+
const sendSessionUnauthorizedResponse = ({
|
|
161
|
+
body,
|
|
162
|
+
oauth,
|
|
163
|
+
res,
|
|
164
|
+
}: SessionUnauthorizedResponseOptions): void => {
|
|
165
|
+
const message = "Unauthorized: No valid session ID provided";
|
|
166
|
+
|
|
167
|
+
res.setHeader("Content-Type", "application/json");
|
|
168
|
+
|
|
169
|
+
const wwwAuthHeader = getWWWAuthenticateHeader(oauth, {
|
|
170
|
+
error: "invalid_token",
|
|
171
|
+
error_description: message,
|
|
172
|
+
});
|
|
173
|
+
if (wwwAuthHeader) {
|
|
174
|
+
res.setHeader("WWW-Authenticate", wwwAuthHeader);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
res.writeHead(401).end(
|
|
178
|
+
JSON.stringify({
|
|
179
|
+
error: {
|
|
180
|
+
code: -32000,
|
|
181
|
+
message,
|
|
182
|
+
},
|
|
183
|
+
id: getRequestId(body),
|
|
184
|
+
jsonrpc: "2.0",
|
|
185
|
+
}),
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
126
189
|
// Helper function to detect scope challenge errors
|
|
127
190
|
const isScopeChallengeError = (
|
|
128
191
|
error: unknown,
|
|
@@ -351,9 +414,9 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
351
414
|
// In stateless mode, ignore session ID header entirely (like Python MCP SDK)
|
|
352
415
|
const sessionId = stateless
|
|
353
416
|
? undefined
|
|
354
|
-
:
|
|
355
|
-
|
|
356
|
-
|
|
417
|
+
: Array.isArray(req.headers["mcp-session-id"])
|
|
418
|
+
? req.headers["mcp-session-id"][0]
|
|
419
|
+
: req.headers["mcp-session-id"];
|
|
357
420
|
|
|
358
421
|
let transport: StreamableHTTPServerTransport;
|
|
359
422
|
|
|
@@ -447,6 +510,12 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
447
510
|
if (sessionId) {
|
|
448
511
|
const activeTransport = activeTransports[sessionId];
|
|
449
512
|
if (!activeTransport) {
|
|
513
|
+
if (authenticate && isJsonRpcBody(body)) {
|
|
514
|
+
sendSessionUnauthorizedResponse({ body, oauth, res });
|
|
515
|
+
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
|
|
450
519
|
res.setHeader("Content-Type", "application/json");
|
|
451
520
|
res
|
|
452
521
|
.writeHead(404)
|
|
@@ -632,6 +701,12 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
632
701
|
|
|
633
702
|
return true;
|
|
634
703
|
} else {
|
|
704
|
+
if (authenticate && isJsonRpcBody(body)) {
|
|
705
|
+
sendSessionUnauthorizedResponse({ body, oauth, res });
|
|
706
|
+
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
|
|
635
710
|
// Error if the server is not created but the request is not an initialize request
|
|
636
711
|
res.setHeader("Content-Type", "application/json");
|
|
637
712
|
|
|
@@ -688,7 +763,7 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
688
763
|
}
|
|
689
764
|
| undefined = sessionId ? activeTransports[sessionId] : undefined;
|
|
690
765
|
|
|
691
|
-
if (!sessionId) {
|
|
766
|
+
if (!sessionId) {
|
|
692
767
|
// Return METHOD_NOT_ALLOWED so stateless clients' transport stops reconnecting
|
|
693
768
|
if (stateless) {
|
|
694
769
|
res.writeHead(405, { Allow: "POST" }).end("Method Not Allowed");
|
|
@@ -696,12 +771,24 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
696
771
|
return true;
|
|
697
772
|
}
|
|
698
773
|
|
|
774
|
+
if (authenticate) {
|
|
775
|
+
sendSessionUnauthorizedResponse({ oauth, res });
|
|
776
|
+
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
|
|
699
780
|
res.writeHead(400).end("No sessionId");
|
|
700
781
|
|
|
701
782
|
return true;
|
|
702
783
|
}
|
|
703
784
|
|
|
704
785
|
if (!activeTransport) {
|
|
786
|
+
if (authenticate) {
|
|
787
|
+
sendSessionUnauthorizedResponse({ oauth, res });
|
|
788
|
+
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
|
|
705
792
|
res.writeHead(400).end("No active transport");
|
|
706
793
|
|
|
707
794
|
return true;
|
|
@@ -733,6 +820,12 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
733
820
|
const sessionId = req.headers["mcp-session-id"] as string | undefined;
|
|
734
821
|
|
|
735
822
|
if (!sessionId) {
|
|
823
|
+
if (authenticate) {
|
|
824
|
+
sendSessionUnauthorizedResponse({ oauth, res });
|
|
825
|
+
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
|
|
736
829
|
res.writeHead(400).end("Invalid or missing sessionId");
|
|
737
830
|
|
|
738
831
|
return true;
|
|
@@ -743,6 +836,12 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
743
836
|
const activeTransport = activeTransports[sessionId];
|
|
744
837
|
|
|
745
838
|
if (!activeTransport) {
|
|
839
|
+
if (authenticate) {
|
|
840
|
+
sendSessionUnauthorizedResponse({ oauth, res });
|
|
841
|
+
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
|
|
746
845
|
res.writeHead(400).end("No active transport");
|
|
747
846
|
return true;
|
|
748
847
|
}
|
|
@@ -881,6 +980,7 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
881
980
|
enableJsonResponse,
|
|
882
981
|
eventStore,
|
|
883
982
|
host = "::",
|
|
983
|
+
keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT,
|
|
884
984
|
oauth,
|
|
885
985
|
onClose,
|
|
886
986
|
onConnect,
|
|
@@ -900,6 +1000,7 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
900
1000
|
enableJsonResponse?: boolean;
|
|
901
1001
|
eventStore?: EventStore;
|
|
902
1002
|
host?: string;
|
|
1003
|
+
keepAliveTimeout?: number;
|
|
903
1004
|
oauth?: AuthConfig["oauth"];
|
|
904
1005
|
onClose?: (server: T) => Promise<void>;
|
|
905
1006
|
onConnect?: (server: T) => Promise<void>;
|
|
@@ -1020,7 +1121,7 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
1020
1121
|
res.writeHead(404).end();
|
|
1021
1122
|
};
|
|
1022
1123
|
|
|
1023
|
-
let httpServer;
|
|
1124
|
+
let httpServer: http.Server | https.Server;
|
|
1024
1125
|
if (sslCa || sslCert || sslKey) {
|
|
1025
1126
|
const options: https.ServerOptions = {};
|
|
1026
1127
|
if (sslCa) {
|
|
@@ -1055,6 +1156,14 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
1055
1156
|
httpServer = http.createServer(requestListener);
|
|
1056
1157
|
}
|
|
1057
1158
|
|
|
1159
|
+
// Keep stateful stream sessions from being torn down when Node closes
|
|
1160
|
+
// otherwise-idle HTTP keep-alive sockets after its 5 second default.
|
|
1161
|
+
httpServer.keepAliveTimeout = keepAliveTimeout;
|
|
1162
|
+
httpServer.headersTimeout = Math.max(
|
|
1163
|
+
httpServer.headersTimeout,
|
|
1164
|
+
keepAliveTimeout + 1000,
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1058
1167
|
await new Promise((resolve) => {
|
|
1059
1168
|
httpServer.listen(port, host, () => {
|
|
1060
1169
|
resolve(undefined);
|