mcp-proxy 6.5.3 → 6.5.5
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 +23 -2
- package/dist/bin/mcp-proxy.mjs +17 -2
- package/dist/bin/mcp-proxy.mjs.map +1 -1
- package/dist/index.d.mts +41 -2
- package/dist/index.mjs +1 -1
- package/dist/{stdio-DNR9B0BZ.mjs → stdio-Be0Fx2iV.mjs} +99 -5
- package/dist/stdio-Be0Fx2iV.mjs.map +1 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/InMemoryEventStore.test.ts +96 -0
- package/src/InMemoryEventStore.ts +51 -0
- package/src/bin/mcp-proxy.ts +21 -2
- package/src/index.ts +2 -1
- package/src/startHTTPServer.test.ts +205 -0
- package/src/startHTTPServer.ts +145 -10
- package/dist/stdio-DNR9B0BZ.mjs.map +0 -1
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -237,4 +237,100 @@ describe("InMemoryEventStore", () => {
|
|
|
237
237
|
secondSpy.mockRestore();
|
|
238
238
|
}
|
|
239
239
|
});
|
|
240
|
+
|
|
241
|
+
it("evicts the oldest events once maxEvents is exceeded", async () => {
|
|
242
|
+
const store = new InMemoryEventStore({ maxEvents: 3 });
|
|
243
|
+
const streamId = "bounded-stream";
|
|
244
|
+
|
|
245
|
+
const eventIds: string[] = [];
|
|
246
|
+
for (let i = 0; i < 5; i++) {
|
|
247
|
+
eventIds.push(
|
|
248
|
+
await store.storeEvent(streamId, {
|
|
249
|
+
id: i,
|
|
250
|
+
jsonrpc: "2.0",
|
|
251
|
+
method: `step/${i}`,
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
254
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Only the 3 most recently stored events survive.
|
|
258
|
+
expect(store.size).toBe(3);
|
|
259
|
+
|
|
260
|
+
// The two oldest events were evicted, so resuming from them is no
|
|
261
|
+
// longer possible - the caller falls back to a fresh stream.
|
|
262
|
+
for (const evictedId of eventIds.slice(0, 2)) {
|
|
263
|
+
const result = await store.replayEventsAfter(evictedId, {
|
|
264
|
+
send: vi.fn(),
|
|
265
|
+
});
|
|
266
|
+
expect(result).toBe("");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Resuming from a surviving event still replays what came after it.
|
|
270
|
+
const replayed: JSONRPCMessage[] = [];
|
|
271
|
+
const returnedStreamId = await store.replayEventsAfter(eventIds[2], {
|
|
272
|
+
send: async (_eventId, message) => {
|
|
273
|
+
replayed.push(message);
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
expect(returnedStreamId).toBe(streamId);
|
|
277
|
+
expect(replayed).toHaveLength(2);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("caps total retained events across all streams, not per stream", async () => {
|
|
281
|
+
const store = new InMemoryEventStore({ maxEvents: 4 });
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < 3; i++) {
|
|
284
|
+
await store.storeEvent("stream-a", {
|
|
285
|
+
id: i,
|
|
286
|
+
jsonrpc: "2.0",
|
|
287
|
+
method: `a/${i}`,
|
|
288
|
+
});
|
|
289
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
290
|
+
}
|
|
291
|
+
for (let i = 0; i < 3; i++) {
|
|
292
|
+
await store.storeEvent("stream-b", {
|
|
293
|
+
id: i,
|
|
294
|
+
jsonrpc: "2.0",
|
|
295
|
+
method: `b/${i}`,
|
|
296
|
+
});
|
|
297
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 6 events were stored across two streams; the store-wide cap still holds.
|
|
301
|
+
expect(store.size).toBe(4);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("defaults to a bounded size when maxEvents is not configured", async () => {
|
|
305
|
+
const store = new InMemoryEventStore();
|
|
306
|
+
const streamId = "default-cap-stream";
|
|
307
|
+
|
|
308
|
+
for (let i = 0; i < 1010; i++) {
|
|
309
|
+
await store.storeEvent(streamId, {
|
|
310
|
+
id: i,
|
|
311
|
+
jsonrpc: "2.0",
|
|
312
|
+
method: "tools/list",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
expect(store.size).toBe(1000);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("rejects a non-positive maxEvents", () => {
|
|
320
|
+
expect(() => new InMemoryEventStore({ maxEvents: 0 })).toThrow(
|
|
321
|
+
/maxEvents must be at least 1/,
|
|
322
|
+
);
|
|
323
|
+
expect(() => new InMemoryEventStore({ maxEvents: -1 })).toThrow(
|
|
324
|
+
/maxEvents must be at least 1/,
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("rejects a NaN maxEvents instead of silently disabling eviction", () => {
|
|
329
|
+
// A naive `maxEvents < 1` check lets NaN through (every comparison with
|
|
330
|
+
// NaN is false), which would make `size > maxEvents` never trigger and
|
|
331
|
+
// silently reintroduce unbounded growth.
|
|
332
|
+
expect(() => new InMemoryEventStore({ maxEvents: NaN })).toThrow(
|
|
333
|
+
/maxEvents must be at least 1/,
|
|
334
|
+
);
|
|
335
|
+
});
|
|
240
336
|
});
|
|
@@ -6,17 +6,56 @@
|
|
|
6
6
|
import type { EventStore } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
7
|
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
|
|
9
|
+
// A stream accumulates one event per server->client message (e.g. every
|
|
10
|
+
// tool call on a stateful session opens its own stream). With no cap, a
|
|
11
|
+
// single long-lived session climbs without bound - see
|
|
12
|
+
// https://github.com/punkpeye/mcp-proxy/issues/72.
|
|
13
|
+
const DEFAULT_MAX_EVENTS = 1000;
|
|
14
|
+
|
|
15
|
+
export interface InMemoryEventStoreOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Maximum number of events retained across all streams before the oldest
|
|
18
|
+
* are evicted (FIFO). Bounds memory use; older reconnect attempts beyond
|
|
19
|
+
* this window fall back to a fresh (non-resumed) stream instead of a replay.
|
|
20
|
+
*
|
|
21
|
+
* @default 1000
|
|
22
|
+
*/
|
|
23
|
+
maxEvents?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
9
26
|
/**
|
|
10
27
|
* Simple in-memory implementation of the EventStore interface for resumability
|
|
11
28
|
* This is primarily intended for examples and testing, not for production use
|
|
12
29
|
* where a persistent storage solution would be more appropriate.
|
|
13
30
|
*/
|
|
14
31
|
export class InMemoryEventStore implements EventStore {
|
|
32
|
+
/**
|
|
33
|
+
* The number of events currently retained (for observability/testing).
|
|
34
|
+
*/
|
|
35
|
+
get size(): number {
|
|
36
|
+
return this.events.size;
|
|
37
|
+
}
|
|
15
38
|
private events: Map<string, { message: JSONRPCMessage; streamId: string }> =
|
|
16
39
|
new Map();
|
|
17
40
|
private lastTimestamp = 0;
|
|
18
41
|
private lastTimestampCounter = 0;
|
|
19
42
|
|
|
43
|
+
private readonly maxEvents: number;
|
|
44
|
+
|
|
45
|
+
constructor(options: InMemoryEventStoreOptions = {}) {
|
|
46
|
+
const maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;
|
|
47
|
+
|
|
48
|
+
// Written as `!(maxEvents >= 1)` rather than `maxEvents < 1` so NaN
|
|
49
|
+
// (every comparison with NaN is false) is rejected too - otherwise
|
|
50
|
+
// `size > maxEvents` would never trigger eviction and silently
|
|
51
|
+
// reintroduce unbounded growth.
|
|
52
|
+
if (!(maxEvents >= 1)) {
|
|
53
|
+
throw new Error("maxEvents must be at least 1");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.maxEvents = maxEvents;
|
|
57
|
+
}
|
|
58
|
+
|
|
20
59
|
/**
|
|
21
60
|
* Replays events that occurred after a specific event ID
|
|
22
61
|
* Implements EventStore.replayEventsAfter
|
|
@@ -77,6 +116,18 @@ export class InMemoryEventStore implements EventStore {
|
|
|
77
116
|
|
|
78
117
|
this.events.set(eventId, { message, streamId });
|
|
79
118
|
|
|
119
|
+
// Map iterates in insertion order, so the first key is always the
|
|
120
|
+
// oldest surviving event - evicting it is a plain FIFO/ring buffer.
|
|
121
|
+
while (this.events.size > this.maxEvents) {
|
|
122
|
+
const oldestEventId = this.events.keys().next().value;
|
|
123
|
+
|
|
124
|
+
if (oldestEventId === undefined) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.events.delete(oldestEventId);
|
|
129
|
+
}
|
|
130
|
+
|
|
80
131
|
return eventId;
|
|
81
132
|
}
|
|
82
133
|
|
package/src/bin/mcp-proxy.ts
CHANGED
|
@@ -14,7 +14,6 @@ import { hideBin } from "yargs/helpers";
|
|
|
14
14
|
const require = createRequire(import.meta.url);
|
|
15
15
|
const packageJson = require("../../package.json") as { version: string };
|
|
16
16
|
|
|
17
|
-
import { InMemoryEventStore } from "../InMemoryEventStore.js";
|
|
18
17
|
import { proxyServer } from "../proxyServer.js";
|
|
19
18
|
import { SSEServer, startHTTPServer } from "../startHTTPServer.js";
|
|
20
19
|
import { StdioClientTransport } from "../StdioClientTransport.js";
|
|
@@ -70,6 +69,18 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
70
69
|
describe: "The endpoint to listen on",
|
|
71
70
|
type: "string",
|
|
72
71
|
},
|
|
72
|
+
eventStore: {
|
|
73
|
+
default: true,
|
|
74
|
+
describe:
|
|
75
|
+
"Enable the streamable HTTP transport's resumability event store, which lets clients replay missed messages after a reconnect. Use --no-eventStore to disable it entirely for request/response-only deployments that don't need this and would rather avoid the memory overhead",
|
|
76
|
+
type: "boolean",
|
|
77
|
+
},
|
|
78
|
+
eventStoreMaxEvents: {
|
|
79
|
+
default: 1000,
|
|
80
|
+
describe:
|
|
81
|
+
"Maximum number of buffered events the resumability event store retains (per session) before it evicts the oldest; bounds memory use. Ignored when --no-eventStore is set",
|
|
82
|
+
type: "number",
|
|
83
|
+
},
|
|
73
84
|
gracefulShutdownTimeout: {
|
|
74
85
|
default: 5000,
|
|
75
86
|
describe: "The timeout (in milliseconds) for graceful shutdown",
|
|
@@ -149,6 +160,13 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
149
160
|
.help()
|
|
150
161
|
.parseAsync();
|
|
151
162
|
|
|
163
|
+
if (!(argv.eventStoreMaxEvents >= 1)) {
|
|
164
|
+
console.error(
|
|
165
|
+
`Error: --eventStoreMaxEvents must be a number >= 1 (got ${String(argv.eventStoreMaxEvents)}). Use --no-eventStore to disable the event store instead.`,
|
|
166
|
+
);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
152
170
|
// Default Access-Control-Allow-Headers list — must stay in sync with
|
|
153
171
|
// `defaultCorsOptions.allowedHeaders` in src/startHTTPServer.ts.
|
|
154
172
|
const DEFAULT_ALLOWED_HEADERS = [
|
|
@@ -248,7 +266,8 @@ const proxy = async () => {
|
|
|
248
266
|
apiKey: argv.apiKey,
|
|
249
267
|
cors: corsOption,
|
|
250
268
|
createServer,
|
|
251
|
-
eventStore:
|
|
269
|
+
eventStore: argv.eventStore ? undefined : false,
|
|
270
|
+
eventStoreMaxEvents: argv.eventStoreMaxEvents,
|
|
252
271
|
host: argv.host,
|
|
253
272
|
keepAliveTimeout: argv.keepAliveTimeout,
|
|
254
273
|
port: argv.port,
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export type { AuthConfig } from "./authentication.js";
|
|
2
2
|
export { AuthenticationMiddleware } from "./authentication.js";
|
|
3
|
+
export type { InMemoryEventStoreOptions } from "./InMemoryEventStore.js";
|
|
3
4
|
export { InMemoryEventStore } from "./InMemoryEventStore.js";
|
|
4
5
|
export { proxyServer } from "./proxyServer.js";
|
|
5
|
-
export type { CorsOptions } from "./startHTTPServer.js";
|
|
6
|
+
export type { CorsOptions, EventStoreOption } from "./startHTTPServer.js";
|
|
6
7
|
export { startHTTPServer } from "./startHTTPServer.js";
|
|
7
8
|
export { ServerType, startStdioServer } from "./startStdioServer.js";
|
|
8
9
|
export { tapTransport } from "./tapTransport.js";
|
|
@@ -993,6 +993,136 @@ it("accepts requests with valid Bearer token in stateless mode", async () => {
|
|
|
993
993
|
await stdioClient.close();
|
|
994
994
|
});
|
|
995
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
|
+
|
|
996
1126
|
it("returns 401 when authenticate callback returns null in stateless mode", async () => {
|
|
997
1127
|
const stdioTransport = new StdioClientTransport({
|
|
998
1128
|
args: ["src/fixtures/simple-stdio-server.ts"],
|
|
@@ -2467,3 +2597,78 @@ it("DELETE request to non-existent session returns 400", async () => {
|
|
|
2467
2597
|
await httpServer.close();
|
|
2468
2598
|
await stdioClient.close();
|
|
2469
2599
|
}, 15000);
|
|
2600
|
+
|
|
2601
|
+
// The SDK only writes a resumability "priming event" (an `id: <eventId>`
|
|
2602
|
+
// SSE line) when an event store is configured, so its presence/absence is a
|
|
2603
|
+
// reliable, wire-level signal of whether resumability is actually active -
|
|
2604
|
+
// see https://github.com/punkpeye/mcp-proxy/issues/72.
|
|
2605
|
+
const initializeAndGetRawBody = async (port: number) => {
|
|
2606
|
+
const response = await fetch(`http://localhost:${port}/mcp`, {
|
|
2607
|
+
body: JSON.stringify({
|
|
2608
|
+
id: 1,
|
|
2609
|
+
jsonrpc: "2.0",
|
|
2610
|
+
method: "initialize",
|
|
2611
|
+
params: {
|
|
2612
|
+
capabilities: {},
|
|
2613
|
+
clientInfo: { name: "test", version: "1.0.0" },
|
|
2614
|
+
// Priming events are only sent to clients whose negotiated protocol
|
|
2615
|
+
// version is >= 2025-11-25.
|
|
2616
|
+
protocolVersion: "2025-11-25",
|
|
2617
|
+
},
|
|
2618
|
+
}),
|
|
2619
|
+
headers: {
|
|
2620
|
+
Accept: "application/json, text/event-stream",
|
|
2621
|
+
"Content-Type": "application/json",
|
|
2622
|
+
},
|
|
2623
|
+
method: "POST",
|
|
2624
|
+
});
|
|
2625
|
+
|
|
2626
|
+
expect(response.status).toBe(200);
|
|
2627
|
+
|
|
2628
|
+
return response.text();
|
|
2629
|
+
};
|
|
2630
|
+
|
|
2631
|
+
it("disables the resumability event store when eventStore: false is passed", async () => {
|
|
2632
|
+
const port = await getRandomPort();
|
|
2633
|
+
|
|
2634
|
+
const httpServer = await startHTTPServer({
|
|
2635
|
+
createServer: async () => {
|
|
2636
|
+
return new Server(
|
|
2637
|
+
{ name: "test", version: "1.0.0" },
|
|
2638
|
+
{ capabilities: {} },
|
|
2639
|
+
);
|
|
2640
|
+
},
|
|
2641
|
+
eventStore: false,
|
|
2642
|
+
port,
|
|
2643
|
+
});
|
|
2644
|
+
|
|
2645
|
+
try {
|
|
2646
|
+
const body = await initializeAndGetRawBody(port);
|
|
2647
|
+
|
|
2648
|
+
expect(body).not.toMatch(/^id: /m);
|
|
2649
|
+
} finally {
|
|
2650
|
+
await httpServer.close();
|
|
2651
|
+
}
|
|
2652
|
+
});
|
|
2653
|
+
|
|
2654
|
+
it("enables a bounded, per-session resumability event store by default", async () => {
|
|
2655
|
+
const port = await getRandomPort();
|
|
2656
|
+
|
|
2657
|
+
const httpServer = await startHTTPServer({
|
|
2658
|
+
createServer: async () => {
|
|
2659
|
+
return new Server(
|
|
2660
|
+
{ name: "test", version: "1.0.0" },
|
|
2661
|
+
{ capabilities: {} },
|
|
2662
|
+
);
|
|
2663
|
+
},
|
|
2664
|
+
port,
|
|
2665
|
+
});
|
|
2666
|
+
|
|
2667
|
+
try {
|
|
2668
|
+
const body = await initializeAndGetRawBody(port);
|
|
2669
|
+
|
|
2670
|
+
expect(body).toMatch(/^id: /m);
|
|
2671
|
+
} finally {
|
|
2672
|
+
await httpServer.close();
|
|
2673
|
+
}
|
|
2674
|
+
});
|