cloakbrowser-mcp 1.3.0 → 1.4.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.
- package/README.md +45 -18
- package/dist/bridge/config.d.ts +1 -1
- package/dist/bridge/config.d.ts.map +1 -1
- package/dist/bridge/config.js +4 -4
- package/dist/bridge/config.js.map +1 -1
- package/dist/bridge/env.d.ts +1 -0
- package/dist/bridge/env.d.ts.map +1 -1
- package/dist/bridge/env.js +3 -0
- package/dist/bridge/env.js.map +1 -1
- package/dist/bridge/tools.d.ts +3 -1
- package/dist/bridge/tools.d.ts.map +1 -1
- package/dist/bridge/tools.js +8 -6
- package/dist/bridge/tools.js.map +1 -1
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +3 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/options.d.ts +6 -1
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/cli/options.js +77 -3
- package/dist/cli/options.js.map +1 -1
- package/dist/cli.js +18 -7
- package/dist/cli.js.map +1 -1
- package/dist/http/nodeServer.d.ts +9 -0
- package/dist/http/nodeServer.d.ts.map +1 -0
- package/dist/http/nodeServer.js +65 -0
- package/dist/http/nodeServer.js.map +1 -0
- package/dist/http/options.d.ts +19 -2
- package/dist/http/options.d.ts.map +1 -1
- package/dist/http/options.js +20 -5
- package/dist/http/options.js.map +1 -1
- package/dist/http/requests.d.ts +11 -0
- package/dist/http/requests.d.ts.map +1 -0
- package/dist/http/requests.js +54 -0
- package/dist/http/requests.js.map +1 -0
- package/dist/http/responses.d.ts +6 -0
- package/dist/http/responses.d.ts.map +1 -0
- package/dist/http/responses.js +24 -0
- package/dist/http/responses.js.map +1 -0
- package/dist/http/server.d.ts +5 -3
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +42 -116
- package/dist/http/server.js.map +1 -1
- package/dist/http/sessionStore.d.ts +3 -1
- package/dist/http/sessionStore.d.ts.map +1 -1
- package/dist/http/sessionStore.js +8 -5
- package/dist/http/sessionStore.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/logging/logger.d.ts +17 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +107 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/protocol/constants.d.ts +5 -0
- package/dist/protocol/constants.d.ts.map +1 -0
- package/dist/protocol/constants.js +5 -0
- package/dist/protocol/constants.js.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +4 -4
- package/package.json +30 -16
- package/server.json +5 -5
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type IncomingMessage, type Server as HttpServer, type ServerResponse } from 'node:http';
|
|
2
|
+
import { type Server as HttpsServer } from 'node:https';
|
|
3
|
+
import { type StreamableHttpOptions } from '#src/http/options';
|
|
4
|
+
export type StreamableNodeServer = HttpServer | HttpsServer;
|
|
5
|
+
export declare function createStreamableNodeServer(options: StreamableHttpOptions, requestListener: (req: IncomingMessage, res: ServerResponse) => void): StreamableNodeServer;
|
|
6
|
+
export declare function formatHost(host: string): string;
|
|
7
|
+
export declare function closeHttpServer(server: StreamableNodeServer): Promise<void>;
|
|
8
|
+
export declare function listenHttpServer(server: StreamableNodeServer, port: number, host: string): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=nodeServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeServer.d.ts","sourceRoot":"","sources":["../../src/http/nodeServer.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,MAAM,IAAI,UAAU,EACzB,KAAK,cAAc,EACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,KAAK,MAAM,IAAI,WAAW,EAE3B,MAAM,YAAY,CAAC;AACpB,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAEpF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,WAAW,CAAC;AAE5D,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,qBAAqB,EAC9B,eAAe,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,GACnE,oBAAoB,CAMtB;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,oBAAoB,EAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAkBf"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createServer as createHttpServer, } from 'node:http';
|
|
3
|
+
import { createServer as createHttpsServer, } from 'node:https';
|
|
4
|
+
import { HTTP_PROTOCOL_HTTPS } from '#src/http/options';
|
|
5
|
+
export function createStreamableNodeServer(options, requestListener) {
|
|
6
|
+
if (options.protocol === HTTP_PROTOCOL_HTTPS) {
|
|
7
|
+
return createHttpsServer(readHttpsServerOptions(options), requestListener);
|
|
8
|
+
}
|
|
9
|
+
// HTTP is an explicit local/reverse-proxy mode; use `https` for direct TLS.
|
|
10
|
+
return createHttpServer(requestListener);
|
|
11
|
+
}
|
|
12
|
+
export function formatHost(host) {
|
|
13
|
+
if (host.startsWith('[') && host.endsWith(']'))
|
|
14
|
+
return host;
|
|
15
|
+
return host.includes(':') ? `[${host}]` : host;
|
|
16
|
+
}
|
|
17
|
+
export async function closeHttpServer(server) {
|
|
18
|
+
if (!server.listening)
|
|
19
|
+
return;
|
|
20
|
+
await new Promise((resolve, reject) => {
|
|
21
|
+
server.close((error) => {
|
|
22
|
+
if (error)
|
|
23
|
+
reject(error);
|
|
24
|
+
else
|
|
25
|
+
resolve();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export async function listenHttpServer(server, port, host) {
|
|
30
|
+
await new Promise((resolve, reject) => {
|
|
31
|
+
const cleanup = () => {
|
|
32
|
+
server.off('error', onError);
|
|
33
|
+
server.off('listening', onListening);
|
|
34
|
+
};
|
|
35
|
+
const onError = (error) => {
|
|
36
|
+
cleanup();
|
|
37
|
+
reject(error);
|
|
38
|
+
};
|
|
39
|
+
const onListening = () => {
|
|
40
|
+
cleanup();
|
|
41
|
+
resolve();
|
|
42
|
+
};
|
|
43
|
+
server.once('error', onError);
|
|
44
|
+
server.once('listening', onListening);
|
|
45
|
+
server.listen(port, host);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function readHttpsServerOptions(options) {
|
|
49
|
+
const { cert, key, pfx, passphrase } = options.tls;
|
|
50
|
+
if (pfx !== undefined) {
|
|
51
|
+
return {
|
|
52
|
+
pfx: readFileSync(pfx),
|
|
53
|
+
passphrase,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (cert !== undefined && key !== undefined) {
|
|
57
|
+
return {
|
|
58
|
+
cert: readFileSync(cert),
|
|
59
|
+
key: readFileSync(key),
|
|
60
|
+
passphrase,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
throw new Error('HTTPS requires either certificate/key files or a PFX file');
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=nodeServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeServer.js","sourceRoot":"","sources":["../../src/http/nodeServer.ts"],"names":[],"mappings":"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"}
|
package/dist/http/options.d.ts
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export declare const BRIDGE_TRANSPORT_STDIO: "stdio";
|
|
2
|
+
export declare const BRIDGE_TRANSPORT_STREAMABLE_HTTP: "streamable-http";
|
|
3
|
+
export declare const HTTP_PROTOCOL_HTTP: "http";
|
|
4
|
+
export declare const HTTP_PROTOCOL_HTTPS: "https";
|
|
5
|
+
export declare const HTTP_SESSION_BACKEND_MEMORY: "memory";
|
|
6
|
+
export declare const HEALTHZ_PATH: "/healthz";
|
|
7
|
+
export declare const READYZ_PATH: "/readyz";
|
|
8
|
+
export type BridgeTransportMode = typeof BRIDGE_TRANSPORT_STDIO | typeof BRIDGE_TRANSPORT_STREAMABLE_HTTP;
|
|
9
|
+
export type HttpProtocol = typeof HTTP_PROTOCOL_HTTP | typeof HTTP_PROTOCOL_HTTPS;
|
|
10
|
+
export type HttpSessionBackend = typeof HTTP_SESSION_BACKEND_MEMORY;
|
|
3
11
|
export declare const bridgeTransportModes: readonly ["stdio", "streamable-http"];
|
|
12
|
+
export declare const httpProtocols: readonly ["http", "https"];
|
|
4
13
|
export declare const httpSessionBackends: readonly ["memory"];
|
|
5
14
|
export declare const streamableHttpProbePaths: readonly ["/healthz", "/readyz"];
|
|
15
|
+
export interface StreamableHttpTlsOptions {
|
|
16
|
+
cert?: string;
|
|
17
|
+
key?: string;
|
|
18
|
+
pfx?: string;
|
|
19
|
+
passphrase?: string;
|
|
20
|
+
}
|
|
6
21
|
export interface StreamableHttpOptions {
|
|
22
|
+
protocol: HttpProtocol;
|
|
7
23
|
host: string;
|
|
8
24
|
port: number;
|
|
9
25
|
endpoint: string;
|
|
10
26
|
authToken?: string;
|
|
27
|
+
tls: StreamableHttpTlsOptions;
|
|
11
28
|
sessionBackend: HttpSessionBackend;
|
|
12
29
|
sessionIdleTtlMs: number;
|
|
13
30
|
sessionMax: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/http/options.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/http/options.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,EAAG,OAAgB,CAAC;AACvD,eAAO,MAAM,gCAAgC,EAAG,iBAA0B,CAAC;AAC3E,eAAO,MAAM,kBAAkB,EAAG,MAAe,CAAC;AAClD,eAAO,MAAM,mBAAmB,EAAG,OAAgB,CAAC;AACpD,eAAO,MAAM,2BAA2B,EAAG,QAAiB,CAAC;AAC7D,eAAO,MAAM,YAAY,EAAG,UAAmB,CAAC;AAChD,eAAO,MAAM,WAAW,EAAG,SAAkB,CAAC;AAE9C,MAAM,MAAM,mBAAmB,GAAG,OAAO,sBAAsB,GAAG,OAAO,gCAAgC,CAAC;AAC1G,MAAM,MAAM,YAAY,GAAG,OAAO,kBAAkB,GAAG,OAAO,mBAAmB,CAAC;AAClF,MAAM,MAAM,kBAAkB,GAAG,OAAO,2BAA2B,CAAC;AAEpE,eAAO,MAAM,oBAAoB,uCAGkB,CAAC;AACpD,eAAO,MAAM,aAAa,4BAGkB,CAAC;AAC7C,eAAO,MAAM,mBAAmB,qBAEkB,CAAC;AACnD,eAAO,MAAM,wBAAwB,kCAAuC,CAAC;AAE7E,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,wBAAwB,CAAC;IAC9B,cAAc,EAAE,kBAAkB,CAAC;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAED,eAAO,MAAM,4BAA4B,EAAE,qBAU1C,CAAC"}
|
package/dist/http/options.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
+
export const BRIDGE_TRANSPORT_STDIO = 'stdio';
|
|
2
|
+
export const BRIDGE_TRANSPORT_STREAMABLE_HTTP = 'streamable-http';
|
|
3
|
+
export const HTTP_PROTOCOL_HTTP = 'http';
|
|
4
|
+
export const HTTP_PROTOCOL_HTTPS = 'https';
|
|
5
|
+
export const HTTP_SESSION_BACKEND_MEMORY = 'memory';
|
|
6
|
+
export const HEALTHZ_PATH = '/healthz';
|
|
7
|
+
export const READYZ_PATH = '/readyz';
|
|
1
8
|
export const bridgeTransportModes = [
|
|
2
|
-
|
|
3
|
-
|
|
9
|
+
BRIDGE_TRANSPORT_STDIO,
|
|
10
|
+
BRIDGE_TRANSPORT_STREAMABLE_HTTP,
|
|
4
11
|
];
|
|
5
|
-
export const
|
|
6
|
-
|
|
12
|
+
export const httpProtocols = [
|
|
13
|
+
HTTP_PROTOCOL_HTTP,
|
|
14
|
+
HTTP_PROTOCOL_HTTPS,
|
|
15
|
+
];
|
|
16
|
+
export const httpSessionBackends = [
|
|
17
|
+
HTTP_SESSION_BACKEND_MEMORY,
|
|
18
|
+
];
|
|
19
|
+
export const streamableHttpProbePaths = [HEALTHZ_PATH, READYZ_PATH];
|
|
7
20
|
export const defaultStreamableHttpOptions = {
|
|
21
|
+
protocol: HTTP_PROTOCOL_HTTP,
|
|
8
22
|
host: '127.0.0.1',
|
|
9
23
|
port: 3000,
|
|
10
24
|
endpoint: '/mcp',
|
|
11
|
-
|
|
25
|
+
tls: {},
|
|
26
|
+
sessionBackend: HTTP_SESSION_BACKEND_MEMORY,
|
|
12
27
|
sessionIdleTtlMs: 3_600_000,
|
|
13
28
|
sessionMax: 32,
|
|
14
29
|
bodyLimitBytes: 1_048_576,
|
package/dist/http/options.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/http/options.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/http/options.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAgB,CAAC;AACvD,MAAM,CAAC,MAAM,gCAAgC,GAAG,iBAA0B,CAAC;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAe,CAAC;AAClD,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAgB,CAAC;AACpD,MAAM,CAAC,MAAM,2BAA2B,GAAG,QAAiB,CAAC;AAC7D,MAAM,CAAC,MAAM,YAAY,GAAG,UAAmB,CAAC;AAChD,MAAM,CAAC,MAAM,WAAW,GAAG,SAAkB,CAAC;AAM9C,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,sBAAsB;IACtB,gCAAgC;CACiB,CAAC;AACpD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,kBAAkB;IAClB,mBAAmB;CACuB,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,2BAA2B;CACqB,CAAC;AACnD,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,YAAY,EAAE,WAAW,CAAU,CAAC;AA2B7E,MAAM,CAAC,MAAM,4BAA4B,GAA0B;IACjE,QAAQ,EAAE,kBAAkB;IAC5B,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,MAAM;IAChB,GAAG,EAAE,EAAE;IACP,cAAc,EAAE,2BAA2B;IAC3C,gBAAgB,EAAE,SAAS;IAC3B,UAAU,EAAE,EAAE;IACd,cAAc,EAAE,SAAS;CAC1B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import { type StreamableHttpOptions } from '#src/http/options';
|
|
3
|
+
export declare function isEndpointRequest(req: IncomingMessage, endpoint: string, fallbackHost: string, protocol?: StreamableHttpOptions['protocol']): boolean;
|
|
4
|
+
export declare function requestPathName(req: IncomingMessage, fallbackHost: string, protocol: StreamableHttpOptions['protocol']): string;
|
|
5
|
+
export declare function hasJsonContentType(req: IncomingMessage): boolean;
|
|
6
|
+
export declare function readJsonBody(req: IncomingMessage, limitBytes: number): Promise<unknown>;
|
|
7
|
+
export declare function containsInitializeRequest(value: unknown): boolean;
|
|
8
|
+
export declare function getSingleHeader(req: IncomingMessage, name: string): string | undefined;
|
|
9
|
+
export declare class RequestBodyTooLargeError extends Error {
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=requests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["../../src/http/requests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAGjD,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,QAAQ,GAAE,qBAAqB,CAAC,UAAU,CAAU,GACnD,OAAO,CAIT;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,eAAe,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,GAC1C,MAAM,CAOR;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAGhE;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB7F;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGjE;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAItF;AAED,qBAAa,wBAAyB,SAAQ,KAAK;CAAG"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { formatHost } from '#src/http/nodeServer';
|
|
3
|
+
import { JSON_CONTENT_TYPE } from '#src/protocol/constants';
|
|
4
|
+
export function isEndpointRequest(req, endpoint, fallbackHost, protocol = 'http') {
|
|
5
|
+
const host = getSingleHeader(req, 'host') ?? formatHost(fallbackHost);
|
|
6
|
+
const url = new URL(req.url ?? '/', `${protocol}://${host}`);
|
|
7
|
+
return url.pathname === endpoint;
|
|
8
|
+
}
|
|
9
|
+
export function requestPathName(req, fallbackHost, protocol) {
|
|
10
|
+
const host = getSingleHeader(req, 'host') ?? formatHost(fallbackHost);
|
|
11
|
+
try {
|
|
12
|
+
return new URL(req.url ?? '/', `${protocol}://${host}`).pathname;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return fallbackPathName(req.url);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function hasJsonContentType(req) {
|
|
19
|
+
const contentType = getSingleHeader(req, 'content-type');
|
|
20
|
+
return contentType?.toLowerCase().includes(JSON_CONTENT_TYPE) ?? false;
|
|
21
|
+
}
|
|
22
|
+
export async function readJsonBody(req, limitBytes) {
|
|
23
|
+
const contentLength = getSingleHeader(req, 'content-length');
|
|
24
|
+
if (contentLength !== undefined && Number.parseInt(contentLength, 10) > limitBytes) {
|
|
25
|
+
throw new RequestBodyTooLargeError();
|
|
26
|
+
}
|
|
27
|
+
const chunks = [];
|
|
28
|
+
let size = 0;
|
|
29
|
+
for await (const chunk of req) {
|
|
30
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
31
|
+
size += buffer.byteLength;
|
|
32
|
+
if (size > limitBytes)
|
|
33
|
+
throw new RequestBodyTooLargeError();
|
|
34
|
+
chunks.push(buffer);
|
|
35
|
+
}
|
|
36
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
37
|
+
}
|
|
38
|
+
export function containsInitializeRequest(value) {
|
|
39
|
+
const messages = Array.isArray(value) ? value : [value];
|
|
40
|
+
return messages.some((message) => isInitializeRequest(message));
|
|
41
|
+
}
|
|
42
|
+
export function getSingleHeader(req, name) {
|
|
43
|
+
const value = req.headers[name];
|
|
44
|
+
if (Array.isArray(value))
|
|
45
|
+
return value[0];
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
export class RequestBodyTooLargeError extends Error {
|
|
49
|
+
}
|
|
50
|
+
function fallbackPathName(url) {
|
|
51
|
+
const pathName = (url ?? '/').split(/[?#]/u, 1)[0];
|
|
52
|
+
return pathName.length > 0 ? pathName : '/';
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=requests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../src/http/requests.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ServerResponse } from 'node:http';
|
|
2
|
+
import { HttpStatus, JsonRpcErrorCode } from '#src/http/status';
|
|
3
|
+
export declare function writeJsonRpcError(res: ServerResponse, status: HttpStatus, code: JsonRpcErrorCode, message: string, headers?: Record<string, string>): void;
|
|
4
|
+
export declare function writeJsonResponse(res: ServerResponse, status: HttpStatus, body: Record<string, unknown>, headers?: Record<string, string>): void;
|
|
5
|
+
export declare function endResponse(res: ServerResponse, body?: string): void;
|
|
6
|
+
//# sourceMappingURL=responses.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/http/responses.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGhE,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAWN;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,IAAI,CAMN;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,SAAK,GAAG,IAAI,CAEhE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { JSON_CONTENT_TYPE, JSON_RPC_VERSION } from '#src/protocol/constants';
|
|
2
|
+
export function writeJsonRpcError(res, status, code, message, headers = {}) {
|
|
3
|
+
const body = JSON.stringify({
|
|
4
|
+
jsonrpc: JSON_RPC_VERSION,
|
|
5
|
+
error: { code, message },
|
|
6
|
+
id: null,
|
|
7
|
+
});
|
|
8
|
+
res.writeHead(status, {
|
|
9
|
+
'Content-Type': JSON_CONTENT_TYPE,
|
|
10
|
+
...headers,
|
|
11
|
+
});
|
|
12
|
+
endResponse(res, body);
|
|
13
|
+
}
|
|
14
|
+
export function writeJsonResponse(res, status, body, headers = {}) {
|
|
15
|
+
res.writeHead(status, {
|
|
16
|
+
'Content-Type': JSON_CONTENT_TYPE,
|
|
17
|
+
...headers,
|
|
18
|
+
});
|
|
19
|
+
endResponse(res, JSON.stringify(body));
|
|
20
|
+
}
|
|
21
|
+
export function endResponse(res, body = '') {
|
|
22
|
+
res.end(body);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=responses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responses.js","sourceRoot":"","sources":["../../src/http/responses.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE9E,MAAM,UAAU,iBAAiB,CAC/B,GAAmB,EACnB,MAAkB,EAClB,IAAsB,EACtB,OAAe,EACf,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,OAAO,EAAE,gBAAgB;QACzB,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;QACxB,EAAE,EAAE,IAAI;KACT,CAAC,CAAC;IACH,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACpB,cAAc,EAAE,iBAAiB;QACjC,GAAG,OAAO;KACX,CAAC,CAAC;IACH,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,GAAmB,EACnB,MAAkB,EAClB,IAA6B,EAC7B,UAAkC,EAAE;IAEpC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACpB,cAAc,EAAE,iBAAiB;QACjC,GAAG,OAAO;KACX,CAAC,CAAC;IACH,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAmB,EAAE,IAAI,GAAG,EAAE;IACxD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
package/dist/http/server.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { type IncomingMessage } from 'node:http';
|
|
2
2
|
import type { AddressInfo } from 'node:net';
|
|
3
3
|
import { type Implementation } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
-
import { type
|
|
5
|
-
import { type
|
|
4
|
+
import { type HttpSessionBackend, type StreamableHttpOptions } from '#src/http/options';
|
|
5
|
+
import { type SessionStore } from '#src/http/sessionStore';
|
|
6
|
+
import type { BridgeLogger } from '#src/logging/logger';
|
|
6
7
|
export interface StartStreamableHttpBridgeOptions extends StreamableHttpOptions {
|
|
7
8
|
serverInfo?: Partial<Implementation>;
|
|
8
9
|
sessionStore?: SessionStore;
|
|
10
|
+
logger?: BridgeLogger;
|
|
9
11
|
}
|
|
10
12
|
export interface StreamableHttpBridgeServer {
|
|
11
13
|
url: string;
|
|
@@ -14,6 +16,6 @@ export interface StreamableHttpBridgeServer {
|
|
|
14
16
|
}
|
|
15
17
|
export declare function startStreamableHttpBridge(options: StartStreamableHttpBridgeOptions): Promise<StreamableHttpBridgeServer>;
|
|
16
18
|
export declare function isAuthorizedRequest(req: IncomingMessage, authToken: string | undefined): boolean;
|
|
17
|
-
export
|
|
19
|
+
export { isEndpointRequest } from '#src/http/requests';
|
|
18
20
|
export type { HttpSessionBackend };
|
|
19
21
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/http/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,eAAe,EAAuB,MAAM,WAAW,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAuBxD,MAAM,WAAW,gCAAiC,SAAQ,qBAAqB;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAQD,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAAC,0BAA0B,CAAC,CAGrC;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAUhG;AAED,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AA6WvD,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/http/server.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
|
-
import { createServer } from 'node:http';
|
|
3
2
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
import { BRIDGE_TRANSPORT_STREAMABLE_HTTP, HEALTHZ_PATH, READYZ_PATH, } from '#src/http/options';
|
|
4
|
+
import { HTTP_SESSION_STATUS_ACTIVE, createSessionStore, } from '#src/http/sessionStore';
|
|
5
|
+
import { HttpStatus, JsonRpcErrorCode } from '#src/http/status';
|
|
6
|
+
import { MCP_SESSION_ID_HEADER } from '#src/protocol/constants';
|
|
7
|
+
import { createBridgeServer } from '#src/server';
|
|
8
|
+
import { closeHttpServer, createStreamableNodeServer, formatHost, listenHttpServer, } from '#src/http/nodeServer';
|
|
9
|
+
import { RequestBodyTooLargeError, containsInitializeRequest, getSingleHeader, hasJsonContentType, isEndpointRequest, readJsonBody, requestPathName, } from '#src/http/requests';
|
|
10
|
+
import { endResponse, writeJsonResponse, writeJsonRpcError } from '#src/http/responses';
|
|
12
11
|
const allowedMethods = 'GET, POST, DELETE';
|
|
13
12
|
export async function startStreamableHttpBridge(options) {
|
|
14
13
|
const controller = new StreamableHttpBridgeController(options);
|
|
@@ -26,11 +25,7 @@ export function isAuthorizedRequest(req, authToken) {
|
|
|
26
25
|
const [scheme, token] = parts;
|
|
27
26
|
return scheme.toLowerCase() === 'bearer' && timingSafeStringEqual(token, authToken);
|
|
28
27
|
}
|
|
29
|
-
export
|
|
30
|
-
const host = getSingleHeader(req, 'host') ?? formatHost(fallbackHost);
|
|
31
|
-
const url = new URL(req.url ?? '/', `http://${host}`);
|
|
32
|
-
return url.pathname === endpoint;
|
|
33
|
-
}
|
|
28
|
+
export { isEndpointRequest } from '#src/http/requests';
|
|
34
29
|
class StreamableHttpBridgeController {
|
|
35
30
|
#options;
|
|
36
31
|
#store;
|
|
@@ -43,9 +38,10 @@ class StreamableHttpBridgeController {
|
|
|
43
38
|
constructor(options) {
|
|
44
39
|
this.#options = options;
|
|
45
40
|
this.#store = options.sessionStore ?? createSessionStore(options.sessionBackend);
|
|
46
|
-
|
|
41
|
+
const requestListener = (req, res) => {
|
|
47
42
|
void this.#handleRequest(req, res);
|
|
48
|
-
}
|
|
43
|
+
};
|
|
44
|
+
this.#httpServer = createStreamableNodeServer(options, requestListener);
|
|
49
45
|
this.#cleanupTimer = setInterval(() => {
|
|
50
46
|
void this.#closeExpiredSessions();
|
|
51
47
|
}, Math.min(options.sessionIdleTtlMs, 60_000));
|
|
@@ -59,7 +55,7 @@ class StreamableHttpBridgeController {
|
|
|
59
55
|
}
|
|
60
56
|
return {
|
|
61
57
|
address,
|
|
62
|
-
url:
|
|
58
|
+
url: `${this.#options.protocol}://${formatHost(address.address)}:${address.port}${this.#options.endpoint}`,
|
|
63
59
|
close: async () => {
|
|
64
60
|
clearInterval(this.#cleanupTimer);
|
|
65
61
|
await Promise.allSettled([...this.#sessions.keys()].map((id) => this.#closeSession(id)));
|
|
@@ -69,16 +65,18 @@ class StreamableHttpBridgeController {
|
|
|
69
65
|
};
|
|
70
66
|
}
|
|
71
67
|
async #handleRequest(req, res) {
|
|
68
|
+
const startedAt = Date.now();
|
|
69
|
+
this.#logRequestOnFinish(req, res, startedAt);
|
|
72
70
|
try {
|
|
73
|
-
if (isEndpointRequest(req,
|
|
71
|
+
if (isEndpointRequest(req, HEALTHZ_PATH, this.#options.host, this.#options.protocol)) {
|
|
74
72
|
this.#handleHealthProbe(req, res);
|
|
75
73
|
return;
|
|
76
74
|
}
|
|
77
|
-
if (isEndpointRequest(req,
|
|
75
|
+
if (isEndpointRequest(req, READYZ_PATH, this.#options.host, this.#options.protocol)) {
|
|
78
76
|
await this.#handleReadinessProbe(req, res);
|
|
79
77
|
return;
|
|
80
78
|
}
|
|
81
|
-
if (!isEndpointRequest(req, this.#options.endpoint, this.#options.host)) {
|
|
79
|
+
if (!isEndpointRequest(req, this.#options.endpoint, this.#options.host, this.#options.protocol)) {
|
|
82
80
|
writeJsonRpcError(res, HttpStatus.NotFound, JsonRpcErrorCode.ServerError, 'Not found');
|
|
83
81
|
return;
|
|
84
82
|
}
|
|
@@ -107,7 +105,7 @@ class StreamableHttpBridgeController {
|
|
|
107
105
|
writeJsonRpcError(res, HttpStatus.InternalServerError, JsonRpcErrorCode.InternalError, 'Internal server error');
|
|
108
106
|
}
|
|
109
107
|
else {
|
|
110
|
-
res
|
|
108
|
+
endResponse(res);
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
}
|
|
@@ -129,7 +127,7 @@ class StreamableHttpBridgeController {
|
|
|
129
127
|
}
|
|
130
128
|
return;
|
|
131
129
|
}
|
|
132
|
-
const sessionId = getSingleHeader(req,
|
|
130
|
+
const sessionId = getSingleHeader(req, MCP_SESSION_ID_HEADER);
|
|
133
131
|
if (sessionId) {
|
|
134
132
|
await this.#handleSessionRequest(req, res, parsedBody);
|
|
135
133
|
return;
|
|
@@ -153,7 +151,7 @@ class StreamableHttpBridgeController {
|
|
|
153
151
|
createdAt: now,
|
|
154
152
|
lastSeenAt: now,
|
|
155
153
|
expiresAt: now + this.#options.sessionIdleTtlMs,
|
|
156
|
-
status:
|
|
154
|
+
status: HTTP_SESSION_STATUS_ACTIVE,
|
|
157
155
|
};
|
|
158
156
|
const transport = new StreamableHTTPServerTransport({
|
|
159
157
|
sessionIdGenerator: () => sessionId,
|
|
@@ -186,7 +184,7 @@ class StreamableHttpBridgeController {
|
|
|
186
184
|
}
|
|
187
185
|
}
|
|
188
186
|
async #handleSessionRequest(req, res, parsedBody) {
|
|
189
|
-
const sessionId = getSingleHeader(req,
|
|
187
|
+
const sessionId = getSingleHeader(req, MCP_SESSION_ID_HEADER);
|
|
190
188
|
if (!sessionId) {
|
|
191
189
|
writeJsonRpcError(res, HttpStatus.BadRequest, JsonRpcErrorCode.ServerError, 'Bad Request: Mcp-Session-Id header is required');
|
|
192
190
|
return;
|
|
@@ -206,9 +204,10 @@ class StreamableHttpBridgeController {
|
|
|
206
204
|
const record = await this.#store.get(sessionId);
|
|
207
205
|
const session = this.#sessions.get(sessionId);
|
|
208
206
|
const now = Date.now();
|
|
209
|
-
if (!record || record.status !==
|
|
210
|
-
if (record?.status ===
|
|
207
|
+
if (!record || record.status !== HTTP_SESSION_STATUS_ACTIVE || record.expiresAt <= now || !session) {
|
|
208
|
+
if (record?.status === HTTP_SESSION_STATUS_ACTIVE && record.expiresAt <= now) {
|
|
211
209
|
await this.#closeSession(sessionId);
|
|
210
|
+
}
|
|
212
211
|
return undefined;
|
|
213
212
|
}
|
|
214
213
|
return session;
|
|
@@ -245,7 +244,7 @@ class StreamableHttpBridgeController {
|
|
|
245
244
|
writeJsonResponse(res, HttpStatus.Ok, {
|
|
246
245
|
status: 'ok',
|
|
247
246
|
version: this.#options.serverInfo?.version ?? 'unknown',
|
|
248
|
-
transport:
|
|
247
|
+
transport: BRIDGE_TRANSPORT_STREAMABLE_HTTP,
|
|
249
248
|
uptimeMs: this.#uptimeMs(),
|
|
250
249
|
});
|
|
251
250
|
}
|
|
@@ -265,7 +264,7 @@ class StreamableHttpBridgeController {
|
|
|
265
264
|
writeJsonResponse(res, ready ? HttpStatus.Ok : HttpStatus.ServiceUnavailable, {
|
|
266
265
|
status: ready ? 'ready' : 'not_ready',
|
|
267
266
|
version: this.#options.serverInfo?.version ?? 'unknown',
|
|
268
|
-
transport:
|
|
267
|
+
transport: BRIDGE_TRANSPORT_STREAMABLE_HTTP,
|
|
269
268
|
uptimeMs: this.#uptimeMs(),
|
|
270
269
|
sessions: {
|
|
271
270
|
active,
|
|
@@ -284,99 +283,26 @@ class StreamableHttpBridgeController {
|
|
|
284
283
|
#uptimeMs() {
|
|
285
284
|
return Math.max(Date.now() - this.#startedAt, 0);
|
|
286
285
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (size > limitBytes)
|
|
303
|
-
throw new RequestBodyTooLargeError();
|
|
304
|
-
chunks.push(buffer);
|
|
286
|
+
#logRequestOnFinish(req, res, startedAt) {
|
|
287
|
+
const logger = this.#options.logger;
|
|
288
|
+
if (!logger)
|
|
289
|
+
return;
|
|
290
|
+
res.once('finish', () => {
|
|
291
|
+
const method = req.method ?? 'UNKNOWN';
|
|
292
|
+
const pathName = requestPathName(req, this.#options.host, this.#options.protocol);
|
|
293
|
+
const durationMs = Math.max(Date.now() - startedAt, 0);
|
|
294
|
+
logger.info({
|
|
295
|
+
duration_ms: durationMs,
|
|
296
|
+
method,
|
|
297
|
+
path: pathName,
|
|
298
|
+
status: res.statusCode,
|
|
299
|
+
}, 'http request');
|
|
300
|
+
});
|
|
305
301
|
}
|
|
306
|
-
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
307
|
-
}
|
|
308
|
-
function containsInitializeRequest(value) {
|
|
309
|
-
const messages = Array.isArray(value) ? value : [value];
|
|
310
|
-
return messages.some((message) => isInitializeRequest(message));
|
|
311
|
-
}
|
|
312
|
-
function getSingleHeader(req, name) {
|
|
313
|
-
const value = req.headers[name];
|
|
314
|
-
if (Array.isArray(value))
|
|
315
|
-
return value[0];
|
|
316
|
-
return value;
|
|
317
|
-
}
|
|
318
|
-
function writeJsonRpcError(res, status, code, message, headers = {}, data) {
|
|
319
|
-
const error = { code, message };
|
|
320
|
-
if (data !== undefined)
|
|
321
|
-
error.data = data;
|
|
322
|
-
res.writeHead(status, {
|
|
323
|
-
'Content-Type': jsonRpcContentType,
|
|
324
|
-
...headers,
|
|
325
|
-
});
|
|
326
|
-
res.end(JSON.stringify({
|
|
327
|
-
jsonrpc: '2.0',
|
|
328
|
-
error,
|
|
329
|
-
id: null,
|
|
330
|
-
}));
|
|
331
|
-
}
|
|
332
|
-
function writeJsonResponse(res, status, body, headers = {}) {
|
|
333
|
-
res.writeHead(status, {
|
|
334
|
-
'Content-Type': jsonRpcContentType,
|
|
335
|
-
...headers,
|
|
336
|
-
});
|
|
337
|
-
res.end(JSON.stringify(body));
|
|
338
|
-
}
|
|
339
|
-
function formatHost(host) {
|
|
340
|
-
if (host.startsWith('[') && host.endsWith(']'))
|
|
341
|
-
return host;
|
|
342
|
-
return host.includes(':') ? `[${host}]` : host;
|
|
343
302
|
}
|
|
344
303
|
function timingSafeStringEqual(actual, expected) {
|
|
345
304
|
const actualBuffer = Buffer.from(actual);
|
|
346
305
|
const expectedBuffer = Buffer.from(expected);
|
|
347
306
|
return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer);
|
|
348
307
|
}
|
|
349
|
-
async function closeHttpServer(server) {
|
|
350
|
-
if (!server.listening)
|
|
351
|
-
return;
|
|
352
|
-
await new Promise((resolve, reject) => {
|
|
353
|
-
server.close((error) => {
|
|
354
|
-
if (error)
|
|
355
|
-
reject(error);
|
|
356
|
-
else
|
|
357
|
-
resolve();
|
|
358
|
-
});
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
async function listenHttpServer(server, port, host) {
|
|
362
|
-
await new Promise((resolve, reject) => {
|
|
363
|
-
const cleanup = () => {
|
|
364
|
-
server.off('error', onError);
|
|
365
|
-
server.off('listening', onListening);
|
|
366
|
-
};
|
|
367
|
-
const onError = (error) => {
|
|
368
|
-
cleanup();
|
|
369
|
-
reject(error);
|
|
370
|
-
};
|
|
371
|
-
const onListening = () => {
|
|
372
|
-
cleanup();
|
|
373
|
-
resolve();
|
|
374
|
-
};
|
|
375
|
-
server.once('error', onError);
|
|
376
|
-
server.once('listening', onListening);
|
|
377
|
-
server.listen(port, host);
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
class RequestBodyTooLargeError extends Error {
|
|
381
|
-
}
|
|
382
308
|
//# sourceMappingURL=server.js.map
|