@frontmcp/testing 1.8.7 → 1.9.1-rc.1

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/perf/index.js CHANGED
@@ -1308,8 +1308,453 @@ function isInterceptedError(err) {
1308
1308
  return typeof err === "object" && err !== null && interceptedErrors.has(err);
1309
1309
  }
1310
1310
 
1311
- // libs/testing/src/transport/streamable-http.transport.ts
1311
+ // libs/testing/src/transport/sse.transport.ts
1312
1312
  var DEFAULT_TIMEOUT = 3e4;
1313
+ var SseTransport = class {
1314
+ baseUrl;
1315
+ entryPath;
1316
+ timeout;
1317
+ authHeaders;
1318
+ publicMode;
1319
+ debug;
1320
+ clientInfo;
1321
+ notificationHandler;
1322
+ interceptors;
1323
+ elicitationHandler;
1324
+ state = "disconnected";
1325
+ authToken;
1326
+ sessionId;
1327
+ messageEndpoint;
1328
+ stream;
1329
+ pending = /* @__PURE__ */ new Map();
1330
+ connectionCount = 0;
1331
+ reconnectCount = 0;
1332
+ lastRequestHeaders = {};
1333
+ constructor(config) {
1334
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
1335
+ this.entryPath = config.entryPath;
1336
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
1337
+ this.authHeaders = config.auth?.headers ?? {};
1338
+ this.authToken = config.auth?.token;
1339
+ this.publicMode = config.publicMode ?? false;
1340
+ this.debug = config.debug ?? false;
1341
+ this.clientInfo = config.clientInfo;
1342
+ this.notificationHandler = config.notificationHandler;
1343
+ this.interceptors = config.interceptors;
1344
+ this.elicitationHandler = config.elicitationHandler;
1345
+ }
1346
+ async connect() {
1347
+ this.state = "connecting";
1348
+ this.connectionCount++;
1349
+ try {
1350
+ if (!this.publicMode && !this.authToken) {
1351
+ await this.requestAnonymousToken();
1352
+ }
1353
+ await this.openStream();
1354
+ this.state = "connected";
1355
+ this.log(`Connected; messages go to ${this.messageEndpoint}`);
1356
+ } catch (error) {
1357
+ this.state = "error";
1358
+ this.stream?.abort();
1359
+ this.stream = void 0;
1360
+ throw error;
1361
+ }
1362
+ }
1363
+ async request(message) {
1364
+ this.ensureConnected();
1365
+ const startTime = Date.now();
1366
+ if (this.interceptors) {
1367
+ const intercepted = await this.interceptors.processRequest(message, {
1368
+ timestamp: /* @__PURE__ */ new Date(),
1369
+ transport: "sse",
1370
+ sessionId: this.sessionId
1371
+ });
1372
+ switch (intercepted.type) {
1373
+ case "mock":
1374
+ return await this.interceptors.processResponse(
1375
+ message,
1376
+ intercepted.response,
1377
+ Date.now() - startTime
1378
+ );
1379
+ case "error":
1380
+ throw markInterceptedError(intercepted.error);
1381
+ case "continue":
1382
+ message = intercepted.request;
1383
+ break;
1384
+ }
1385
+ }
1386
+ let response;
1387
+ if (message.id === void 0) {
1388
+ await this.post(JSON.stringify(message));
1389
+ response = { jsonrpc: "2.0", id: null, result: void 0 };
1390
+ } else {
1391
+ response = await this.postAndAwait(message.id, JSON.stringify(message));
1392
+ }
1393
+ if (this.interceptors) {
1394
+ response = await this.interceptors.processResponse(message, response, Date.now() - startTime);
1395
+ }
1396
+ return response;
1397
+ }
1398
+ async notify(message) {
1399
+ this.ensureConnected();
1400
+ const result = await this.post(JSON.stringify(message));
1401
+ if (!result.ok) this.log(`HTTP ${result.status} on notification: ${result.body}`);
1402
+ }
1403
+ async sendRaw(data) {
1404
+ this.ensureConnected();
1405
+ let id;
1406
+ try {
1407
+ const parsed = JSON.parse(data);
1408
+ if (typeof parsed.id === "string" || typeof parsed.id === "number") id = parsed.id;
1409
+ } catch {
1410
+ }
1411
+ if (id !== void 0) return this.postAndAwait(id, data);
1412
+ const result = await this.post(data);
1413
+ if (!result.ok) {
1414
+ return { jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error", data: result.body } };
1415
+ }
1416
+ return { jsonrpc: "2.0", id: null, result: void 0 };
1417
+ }
1418
+ async close() {
1419
+ this.closeStream("Transport closed");
1420
+ this.state = "disconnected";
1421
+ this.sessionId = void 0;
1422
+ this.messageEndpoint = void 0;
1423
+ this.log("SSE transport closed");
1424
+ }
1425
+ isConnected() {
1426
+ return this.state === "connected";
1427
+ }
1428
+ getState() {
1429
+ return this.state;
1430
+ }
1431
+ getSessionId() {
1432
+ return this.sessionId;
1433
+ }
1434
+ setAuthToken(token) {
1435
+ this.authToken = token;
1436
+ }
1437
+ setTimeout(ms) {
1438
+ this.timeout = ms;
1439
+ }
1440
+ getMessageEndpoint() {
1441
+ return this.messageEndpoint;
1442
+ }
1443
+ getConnectionCount() {
1444
+ return this.connectionCount;
1445
+ }
1446
+ getReconnectCount() {
1447
+ return this.reconnectCount;
1448
+ }
1449
+ getLastRequestHeaders() {
1450
+ return { ...this.lastRequestHeaders };
1451
+ }
1452
+ setInterceptors(interceptors2) {
1453
+ this.interceptors = interceptors2;
1454
+ }
1455
+ getInterceptors() {
1456
+ return this.interceptors;
1457
+ }
1458
+ setElicitationHandler(handler) {
1459
+ this.elicitationHandler = handler;
1460
+ }
1461
+ async simulateDisconnect() {
1462
+ this.closeStream("Simulated disconnect");
1463
+ this.state = "disconnected";
1464
+ this.sessionId = void 0;
1465
+ this.messageEndpoint = void 0;
1466
+ }
1467
+ async waitForReconnect(timeoutMs) {
1468
+ this.reconnectCount++;
1469
+ let timer;
1470
+ try {
1471
+ await Promise.race([
1472
+ this.connect(),
1473
+ new Promise((_, reject) => {
1474
+ timer = setTimeout(() => reject(new Error("Timeout waiting for reconnection")), timeoutMs);
1475
+ })
1476
+ ]);
1477
+ } finally {
1478
+ clearTimeout(timer);
1479
+ }
1480
+ }
1481
+ // ═══════════════════════════════════════════════════════════════════
1482
+ // STREAM
1483
+ // ═══════════════════════════════════════════════════════════════════
1484
+ /** Open `GET <entry>/sse` and wait for the `endpoint` event. */
1485
+ async openStream() {
1486
+ const controller = new AbortController();
1487
+ this.stream = controller;
1488
+ const url = this.sseUrl();
1489
+ const { "Content-Type": _contentType, ...headers } = this.buildHeaders();
1490
+ this.log(`GET ${url}`);
1491
+ const connectTimer = setTimeout(() => controller.abort(), this.timeout);
1492
+ let response;
1493
+ try {
1494
+ response = await fetch(url, {
1495
+ method: "GET",
1496
+ headers: { ...headers, Accept: "text/event-stream" },
1497
+ signal: controller.signal
1498
+ });
1499
+ } catch (error) {
1500
+ clearTimeout(connectTimer);
1501
+ throw new Error(
1502
+ isAbortError(error) ? `SSE connection to ${url} timed out after ${this.timeout}ms` : `SSE connection to ${url} failed: ${errorMessage(error)}`,
1503
+ { cause: error }
1504
+ );
1505
+ }
1506
+ const isEventStream = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
1507
+ if (!response.ok || !response.body || !isEventStream) {
1508
+ clearTimeout(connectTimer);
1509
+ const body = await response.text().catch(() => "");
1510
+ throw new Error(
1511
+ `SSE connection to ${url} failed: HTTP ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}. The legacy HTTP+SSE transport needs a server that serves it (@FrontMcp({ transport: { protocol: { legacy: true } } })).`
1512
+ );
1513
+ }
1514
+ const endpoint = new Promise((resolve, reject) => {
1515
+ const reader = response.body?.getReader();
1516
+ if (!reader) {
1517
+ reject(new Error(`SSE connection to ${url} has no body`));
1518
+ return;
1519
+ }
1520
+ void this.readStream(controller, reader, resolve, reject);
1521
+ });
1522
+ try {
1523
+ const data = await endpoint;
1524
+ const messageUrl = new URL(data, url);
1525
+ this.messageEndpoint = messageUrl.toString();
1526
+ this.sessionId = messageUrl.searchParams.get("sessionId") ?? void 0;
1527
+ } finally {
1528
+ clearTimeout(connectTimer);
1529
+ }
1530
+ }
1531
+ async readStream(controller, reader, onEndpoint, onEndpointError) {
1532
+ const decoder = new TextDecoder();
1533
+ let buffer = "";
1534
+ let endpointSeen = false;
1535
+ try {
1536
+ for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
1537
+ const { completeEvents, remainder } = splitSSEEvents(buffer + decoder.decode(chunk.value, { stream: true }));
1538
+ buffer = remainder;
1539
+ for (const text of completeEvents) {
1540
+ const event = parseSSEEvent(text);
1541
+ if (!event) continue;
1542
+ if (event.type === "endpoint") {
1543
+ if (!endpointSeen) {
1544
+ endpointSeen = true;
1545
+ onEndpoint(event.data);
1546
+ }
1547
+ continue;
1548
+ }
1549
+ await this.handleMessage(event.data);
1550
+ }
1551
+ }
1552
+ } catch (error) {
1553
+ if (!controller.signal.aborted) this.log("SSE stream failed:", error);
1554
+ } finally {
1555
+ reader.releaseLock();
1556
+ }
1557
+ if (!endpointSeen) {
1558
+ onEndpointError(
1559
+ new Error(
1560
+ controller.signal.aborted ? `SSE stream at ${this.sseUrl()} sent no endpoint event within ${this.timeout}ms` : `SSE stream at ${this.sseUrl()} ended before the server sent its endpoint`
1561
+ )
1562
+ );
1563
+ }
1564
+ if (this.stream === controller) {
1565
+ this.stream = void 0;
1566
+ this.rejectPending("SSE stream closed by the server");
1567
+ if (this.state === "connected") this.state = "disconnected";
1568
+ }
1569
+ }
1570
+ async handleMessage(data) {
1571
+ let message;
1572
+ try {
1573
+ message = JSON.parse(data);
1574
+ } catch {
1575
+ this.log("Ignoring SSE event that is not JSON:", data);
1576
+ return;
1577
+ }
1578
+ if (!message || typeof message !== "object") return;
1579
+ if (typeof message.method === "string") {
1580
+ const request = message;
1581
+ if (request.id === void 0 || request.id === null) {
1582
+ this.notificationHandler?.(request);
1583
+ return;
1584
+ }
1585
+ await this.answerServerRequest(request);
1586
+ return;
1587
+ }
1588
+ if (message.id !== void 0 && message.id !== null) {
1589
+ const pending = this.pending.get(message.id);
1590
+ if (pending) {
1591
+ clearTimeout(pending.timer);
1592
+ this.pending.delete(message.id);
1593
+ pending.resolve(message);
1594
+ }
1595
+ }
1596
+ }
1597
+ /** Server→client requests: elicitation goes to the handler, `ping` is answered, the rest refused. */
1598
+ async answerServerRequest(request) {
1599
+ const id = request.id;
1600
+ let reply;
1601
+ if (request.method === "elicitation/create") {
1602
+ reply = { jsonrpc: "2.0", id, result: await this.elicit(request.params) };
1603
+ } else if (request.method === "ping") {
1604
+ reply = { jsonrpc: "2.0", id, result: {} };
1605
+ } else {
1606
+ reply = { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${request.method}` } };
1607
+ }
1608
+ const result = await this.post(JSON.stringify(reply));
1609
+ if (!result.ok) this.log(`HTTP ${result.status} answering ${request.method}: ${result.body}`);
1610
+ }
1611
+ async elicit(params) {
1612
+ if (!this.elicitationHandler) return { action: "decline" };
1613
+ try {
1614
+ return await this.elicitationHandler(params);
1615
+ } catch (error) {
1616
+ this.log("Elicitation handler error:", error);
1617
+ return { action: "cancel" };
1618
+ }
1619
+ }
1620
+ closeStream(reason) {
1621
+ const controller = this.stream;
1622
+ this.stream = void 0;
1623
+ controller?.abort();
1624
+ this.rejectPending(reason);
1625
+ }
1626
+ rejectPending(reason) {
1627
+ for (const [id, pending] of this.pending) {
1628
+ clearTimeout(pending.timer);
1629
+ pending.resolve({ jsonrpc: "2.0", id, error: { code: -32e3, message: reason } });
1630
+ }
1631
+ this.pending.clear();
1632
+ }
1633
+ // ═══════════════════════════════════════════════════════════════════
1634
+ // POSTING
1635
+ // ═══════════════════════════════════════════════════════════════════
1636
+ /** POST a message and resolve with the response the stream carries for `id`. */
1637
+ async postAndAwait(id, body) {
1638
+ const answer = new Promise((resolve) => {
1639
+ const timer = setTimeout(() => {
1640
+ this.pending.delete(id);
1641
+ resolve({ jsonrpc: "2.0", id, error: { code: -32e3, message: `Request timeout after ${this.timeout}ms` } });
1642
+ }, this.timeout);
1643
+ this.pending.set(id, { resolve, timer });
1644
+ });
1645
+ const result = await this.post(body);
1646
+ if (!result.ok) {
1647
+ const pending = this.pending.get(id);
1648
+ if (pending) {
1649
+ clearTimeout(pending.timer);
1650
+ this.pending.delete(id);
1651
+ }
1652
+ return {
1653
+ jsonrpc: "2.0",
1654
+ id,
1655
+ error: { code: -32e3, message: `HTTP ${result.status}: ${result.statusText}`, data: result.body }
1656
+ };
1657
+ }
1658
+ return answer;
1659
+ }
1660
+ async post(body) {
1661
+ const endpoint = this.messageEndpoint;
1662
+ if (!endpoint) throw new Error("SSE transport has no message endpoint. Call connect() first.");
1663
+ const headers = this.buildHeaders();
1664
+ this.lastRequestHeaders = headers;
1665
+ this.log(`POST ${endpoint}`, body);
1666
+ const controller = new AbortController();
1667
+ const timer = setTimeout(() => controller.abort(), this.timeout);
1668
+ try {
1669
+ const response = await fetch(endpoint, { method: "POST", headers, body, signal: controller.signal });
1670
+ const text = await response.text();
1671
+ return { ok: response.ok, status: response.status, statusText: response.statusText, body: text };
1672
+ } catch (error) {
1673
+ if (isAbortError(error)) {
1674
+ return { ok: false, status: 0, statusText: `timeout after ${this.timeout}ms`, body: "" };
1675
+ }
1676
+ throw error;
1677
+ } finally {
1678
+ clearTimeout(timer);
1679
+ }
1680
+ }
1681
+ // ═══════════════════════════════════════════════════════════════════
1682
+ // HELPERS
1683
+ // ═══════════════════════════════════════════════════════════════════
1684
+ async requestAnonymousToken() {
1685
+ const url = new URL(this.baseUrl);
1686
+ url.pathname = `${url.pathname.replace(/\/+$/, "")}/oauth/token`;
1687
+ url.search = "";
1688
+ const tokenUrl = url.toString();
1689
+ try {
1690
+ const response = await fetch(tokenUrl, {
1691
+ method: "POST",
1692
+ headers: { "Content-Type": "application/json" },
1693
+ body: JSON.stringify({ grant_type: "anonymous", client_id: crypto.randomUUID(), resource: this.baseUrl })
1694
+ });
1695
+ if (!response.ok) {
1696
+ this.log(`No anonymous token (HTTP ${response.status})`);
1697
+ return;
1698
+ }
1699
+ const token = await response.json();
1700
+ if (token.access_token) this.authToken = token.access_token;
1701
+ } catch (error) {
1702
+ this.log("Error requesting anonymous token:", error);
1703
+ }
1704
+ }
1705
+ /** `<baseUrl><entryPath>/sse`, built from URL parts so query params on `baseUrl` survive. */
1706
+ sseUrl() {
1707
+ const url = new URL(this.baseUrl);
1708
+ const entry = (this.entryPath ?? "").replace(/^\/+|\/+$/g, "");
1709
+ const basePath = url.pathname.replace(/\/+$/, "");
1710
+ url.pathname = entry ? `${basePath}/${entry}/sse` : `${basePath}/sse`;
1711
+ return url.toString();
1712
+ }
1713
+ buildHeaders() {
1714
+ const headers = {
1715
+ "Content-Type": "application/json",
1716
+ Accept: "application/json, text/event-stream"
1717
+ };
1718
+ if (this.clientInfo) headers["User-Agent"] = `${this.clientInfo.name}/${this.clientInfo.version}`;
1719
+ if (this.authToken) headers["Authorization"] = `Bearer ${this.authToken}`;
1720
+ if (this.sessionId) headers["mcp-session-id"] = this.sessionId;
1721
+ Object.assign(headers, this.authHeaders);
1722
+ return headers;
1723
+ }
1724
+ ensureConnected() {
1725
+ if (this.state !== "connected") {
1726
+ throw new Error("Transport not connected. Call connect() first.");
1727
+ }
1728
+ }
1729
+ log(message, data) {
1730
+ if (this.debug) {
1731
+ console.log(`[SSE] ${message}`, data ?? "");
1732
+ }
1733
+ }
1734
+ };
1735
+ function splitSSEEvents(buffer) {
1736
+ const endsWithCarriageReturn = buffer.endsWith("\r");
1737
+ const normalized = (endsWithCarriageReturn ? buffer.slice(0, -1) : buffer).replace(/\r\n?/g, "\n");
1738
+ const completeEvents = normalized.split("\n\n");
1739
+ const remainder = completeEvents.pop() ?? "";
1740
+ return { completeEvents, remainder: endsWithCarriageReturn ? `${remainder}\r` : remainder };
1741
+ }
1742
+ function parseSSEEvent(text) {
1743
+ let type = "message";
1744
+ const data = [];
1745
+ for (const line of text.split("\n")) {
1746
+ if (line.startsWith(":")) continue;
1747
+ const colon = line.indexOf(":");
1748
+ const field = colon === -1 ? line : line.slice(0, colon);
1749
+ const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
1750
+ if (field === "event") type = value;
1751
+ else if (field === "data") data.push(value);
1752
+ }
1753
+ return data.length > 0 ? { type, data: data.join("\n") } : void 0;
1754
+ }
1755
+
1756
+ // libs/testing/src/transport/streamable-http.transport.ts
1757
+ var DEFAULT_TIMEOUT2 = 3e4;
1313
1758
  var NOTIFICATION_STREAM_REOPEN_DELAY_MS = 250;
1314
1759
  var StreamableHttpTransport = class {
1315
1760
  config;
@@ -1331,7 +1776,7 @@ var StreamableHttpTransport = class {
1331
1776
  baseUrl: config.baseUrl.replace(/\/$/, ""),
1332
1777
  // Remove trailing slash
1333
1778
  entryPath: config.entryPath,
1334
- timeout: config.timeout ?? DEFAULT_TIMEOUT,
1779
+ timeout: config.timeout ?? DEFAULT_TIMEOUT2,
1335
1780
  auth: config.auth ?? {},
1336
1781
  publicMode: config.publicMode ?? false,
1337
1782
  debug: config.debug ?? false,
@@ -1671,7 +2116,7 @@ var StreamableHttpTransport = class {
1671
2116
  let buffer = "";
1672
2117
  try {
1673
2118
  for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
1674
- const { completeEvents, remainder } = splitSSEEvents(buffer + decoder.decode(chunk.value, { stream: true }));
2119
+ const { completeEvents, remainder } = splitSSEEvents2(buffer + decoder.decode(chunk.value, { stream: true }));
1675
2120
  buffer = remainder;
1676
2121
  for (const eventText of completeEvents) {
1677
2122
  for (const event of this.parseSSEEvents(eventText, void 0).events) {
@@ -2005,7 +2450,7 @@ var StreamableHttpTransport = class {
2005
2450
  };
2006
2451
  }
2007
2452
  };
2008
- function splitSSEEvents(buffer) {
2453
+ function splitSSEEvents2(buffer) {
2009
2454
  const endsWithCarriageReturn = buffer.endsWith("\r");
2010
2455
  const normalized = (endsWithCarriageReturn ? buffer.slice(0, -1) : buffer).replace(/\r\n?/g, "\n");
2011
2456
  const completeEvents = normalized.split("\n\n");
@@ -2243,7 +2688,7 @@ var McpTestClientBuilder = class {
2243
2688
  };
2244
2689
 
2245
2690
  // libs/testing/src/client/mcp-test-client.ts
2246
- var DEFAULT_TIMEOUT2 = 3e4;
2691
+ var DEFAULT_TIMEOUT3 = 3e4;
2247
2692
  var DEFAULT_PROTOCOL_VERSION = "2025-06-18";
2248
2693
  var UNSUPPORTED_PROTOCOL_VERSIONS = ["2026-07-28"];
2249
2694
  var DEFAULT_CLIENT_INFO = {
@@ -2313,7 +2758,7 @@ var McpTestClient = class {
2313
2758
  transport: config.transport ?? "streamable-http",
2314
2759
  auth: config.auth ?? {},
2315
2760
  publicMode: config.publicMode ?? false,
2316
- timeout: config.timeout ?? DEFAULT_TIMEOUT2,
2761
+ timeout: config.timeout ?? DEFAULT_TIMEOUT3,
2317
2762
  debug: config.debug ?? false,
2318
2763
  protocolVersion: config.protocolVersion ?? DEFAULT_PROTOCOL_VERSION,
2319
2764
  clientInfo: config.clientInfo ?? DEFAULT_CLIENT_INFO,
@@ -3079,7 +3524,18 @@ var McpTestClient = class {
3079
3524
  notificationHandler: this.recordNotification
3080
3525
  });
3081
3526
  case "sse":
3082
- throw new Error("SSE transport not yet implemented");
3527
+ return new SseTransport({
3528
+ baseUrl,
3529
+ entryPath: this.config.entryPath,
3530
+ timeout: this.config.timeout,
3531
+ auth: this.config.auth,
3532
+ publicMode: this.config.publicMode,
3533
+ debug: this.config.debug,
3534
+ interceptors: this._interceptors,
3535
+ clientInfo: this.config.clientInfo,
3536
+ elicitationHandler: this._elicitationHandler,
3537
+ notificationHandler: this.recordNotification
3538
+ });
3083
3539
  default:
3084
3540
  throw new Error(`Unknown transport type: ${this.config.transport}`);
3085
3541
  }
@@ -4,4 +4,5 @@
4
4
  */
5
5
  export type { McpTransport, TransportConfig, TransportState, JsonRpcRequest, JsonRpcResponse, NotificationHandler, } from './transport.interface';
6
6
  export { StreamableHttpTransport } from './streamable-http.transport';
7
+ export { SseTransport } from './sse.transport';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @file sse.transport.ts
3
+ * @description Legacy HTTP+SSE transport (MCP 2024-11-05) for the MCP Test Client
4
+ *
5
+ * The client opens a long-lived `GET <entry>/sse` stream. The server's first event, `endpoint`,
6
+ * names the URL to POST JSON-RPC messages to (`<entry>/message?sessionId=…`). Every POST is
7
+ * answered `202 Accepted`; the JSON-RPC response arrives later as a `message` event on the
8
+ * stream, as do notifications and server→client requests (e.g. `elicitation/create`).
9
+ */
10
+ import type { ElicitationHandler } from '../client/mcp-test-client.types';
11
+ import type { InterceptorChain } from '../interceptor';
12
+ import type { JsonRpcRequest, JsonRpcResponse, McpTransport, TransportConfig, TransportState } from './transport.interface';
13
+ /**
14
+ * Legacy HTTP+SSE transport.
15
+ *
16
+ * Needs a server that serves the legacy SSE endpoints (`transport.protocol` with `legacy: true`,
17
+ * or `sse: true`). Requests are correlated with their responses by JSON-RPC id.
18
+ */
19
+ export declare class SseTransport implements McpTransport {
20
+ private readonly baseUrl;
21
+ private readonly entryPath?;
22
+ private timeout;
23
+ private readonly authHeaders;
24
+ private readonly publicMode;
25
+ private readonly debug;
26
+ private readonly clientInfo?;
27
+ private readonly notificationHandler?;
28
+ private interceptors?;
29
+ private elicitationHandler?;
30
+ private state;
31
+ private authToken;
32
+ private sessionId;
33
+ private messageEndpoint;
34
+ private stream;
35
+ private readonly pending;
36
+ private connectionCount;
37
+ private reconnectCount;
38
+ private lastRequestHeaders;
39
+ constructor(config: TransportConfig);
40
+ connect(): Promise<void>;
41
+ request<T = unknown>(message: JsonRpcRequest): Promise<JsonRpcResponse & {
42
+ result?: T;
43
+ }>;
44
+ notify(message: JsonRpcRequest): Promise<void>;
45
+ sendRaw(data: string): Promise<JsonRpcResponse>;
46
+ close(): Promise<void>;
47
+ isConnected(): boolean;
48
+ getState(): TransportState;
49
+ getSessionId(): string | undefined;
50
+ setAuthToken(token: string): void;
51
+ setTimeout(ms: number): void;
52
+ getMessageEndpoint(): string | undefined;
53
+ getConnectionCount(): number;
54
+ getReconnectCount(): number;
55
+ getLastRequestHeaders(): Record<string, string>;
56
+ setInterceptors(interceptors: InterceptorChain): void;
57
+ getInterceptors(): InterceptorChain | undefined;
58
+ setElicitationHandler(handler: ElicitationHandler | undefined): void;
59
+ simulateDisconnect(): Promise<void>;
60
+ waitForReconnect(timeoutMs: number): Promise<void>;
61
+ /** Open `GET <entry>/sse` and wait for the `endpoint` event. */
62
+ private openStream;
63
+ private readStream;
64
+ private handleMessage;
65
+ /** Server→client requests: elicitation goes to the handler, `ping` is answered, the rest refused. */
66
+ private answerServerRequest;
67
+ private elicit;
68
+ private closeStream;
69
+ private rejectPending;
70
+ /** POST a message and resolve with the response the stream carries for `id`. */
71
+ private postAndAwait;
72
+ private post;
73
+ private requestAnonymousToken;
74
+ /** `<baseUrl><entryPath>/sse`, built from URL parts so query params on `baseUrl` survive. */
75
+ private sseUrl;
76
+ private buildHeaders;
77
+ private ensureConnected;
78
+ private log;
79
+ }
80
+ //# sourceMappingURL=sse.transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.transport.d.ts","sourceRoot":"","sources":["../../src/transport/sse.transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAwC,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAChH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,YAAY,EAEZ,eAAe,EACf,cAAc,EACf,MAAM,uBAAuB,CAAC;AAc/B;;;;;GAKG;AACH,qBAAa,YAAa,YAAW,YAAY;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IACpC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAsB;IAC3D,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,OAAO,CAAC,kBAAkB,CAAC,CAAqB;IAEhD,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8C;IACtE,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,kBAAkB,CAA8B;gBAE5C,MAAM,EAAE,eAAe;IAc7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBxB,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG;QAAE,MAAM,CAAC,EAAE,CAAC,CAAA;KAAE,CAAC;IAuCxF,MAAM,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAkB/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,WAAW,IAAI,OAAO;IAItB,QAAQ,IAAI,cAAc;IAI1B,YAAY,IAAI,MAAM,GAAG,SAAS;IAIlC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIjC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI5B,kBAAkB,IAAI,MAAM,GAAG,SAAS;IAIxC,kBAAkB,IAAI,MAAM;IAI5B,iBAAiB,IAAI,MAAM;IAI3B,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAI/C,eAAe,CAAC,YAAY,EAAE,gBAAgB,GAAG,IAAI;IAIrD,eAAe,IAAI,gBAAgB,GAAG,SAAS;IAI/C,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,GAAG,IAAI;IAI9D,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOnC,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxD,gEAAgE;YAClD,UAAU;YAsDV,UAAU;YAgDV,aAAa;IA8B3B,qGAAqG;YACvF,mBAAmB;YAcnB,MAAM;IAYpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,aAAa;IAYrB,gFAAgF;YAClE,YAAY;YAyBZ,IAAI;YA2BJ,qBAAqB;IAuBnC,6FAA6F;IAC7F,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,GAAG;CAKZ"}