@frontmcp/testing 1.8.0 → 1.8.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/client/index.d.ts +1 -1
- package/client/index.d.ts.map +1 -1
- package/client/mcp-test-client.builder.d.ts +2 -1
- package/client/mcp-test-client.builder.d.ts.map +1 -1
- package/client/mcp-test-client.d.ts +6 -2
- package/client/mcp-test-client.d.ts.map +1 -1
- package/client/mcp-test-client.types.d.ts +8 -1
- package/client/mcp-test-client.types.d.ts.map +1 -1
- package/esm/fixtures/index.mjs +127 -6
- package/esm/index.mjs +132 -12
- package/esm/matchers/index.mjs +5 -6
- package/esm/package.json +6 -6
- package/esm/perf/index.mjs +127 -6
- package/esm/setup.mjs +5 -6
- package/fixtures/index.js +127 -6
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +132 -12
- package/matchers/index.js +5 -6
- package/matchers/matcher-types.d.ts +3 -2
- package/matchers/matcher-types.d.ts.map +1 -1
- package/matchers/mcp-matchers.d.ts +1 -1
- package/matchers/mcp-matchers.d.ts.map +1 -1
- package/package.json +6 -6
- package/perf/index.js +127 -6
- package/setup.js +5 -6
- package/transport/index.d.ts +1 -1
- package/transport/index.d.ts.map +1 -1
- package/transport/streamable-http.transport.d.ts +10 -0
- package/transport/streamable-http.transport.d.ts.map +1 -1
- package/transport/transport.interface.d.ts +9 -0
- package/transport/transport.interface.d.ts.map +1 -1
package/fixtures/index.js
CHANGED
|
@@ -489,6 +489,7 @@ function sleep(ms) {
|
|
|
489
489
|
|
|
490
490
|
// libs/testing/src/transport/streamable-http.transport.ts
|
|
491
491
|
var DEFAULT_TIMEOUT = 3e4;
|
|
492
|
+
var NOTIFICATION_STREAM_REOPEN_DELAY_MS = 250;
|
|
492
493
|
var StreamableHttpTransport = class {
|
|
493
494
|
config;
|
|
494
495
|
state = "disconnected";
|
|
@@ -500,6 +501,10 @@ var StreamableHttpTransport = class {
|
|
|
500
501
|
interceptors;
|
|
501
502
|
publicMode;
|
|
502
503
|
elicitationHandler;
|
|
504
|
+
notificationHandler;
|
|
505
|
+
notificationStream;
|
|
506
|
+
notificationProtocolVersion = "";
|
|
507
|
+
notificationStreamReopenTimer;
|
|
503
508
|
constructor(config) {
|
|
504
509
|
this.config = {
|
|
505
510
|
baseUrl: config.baseUrl.replace(/\/$/, ""),
|
|
@@ -516,6 +521,7 @@ var StreamableHttpTransport = class {
|
|
|
516
521
|
this.interceptors = config.interceptors;
|
|
517
522
|
this.publicMode = config.publicMode ?? false;
|
|
518
523
|
this.elicitationHandler = config.elicitationHandler;
|
|
524
|
+
this.notificationHandler = config.notificationHandler;
|
|
519
525
|
}
|
|
520
526
|
async connect() {
|
|
521
527
|
this.state = "connecting";
|
|
@@ -735,7 +741,12 @@ var StreamableHttpTransport = class {
|
|
|
735
741
|
};
|
|
736
742
|
}
|
|
737
743
|
}
|
|
744
|
+
async openNotificationStream(protocolVersion) {
|
|
745
|
+
this.notificationProtocolVersion = protocolVersion;
|
|
746
|
+
await this.connectNotificationStream();
|
|
747
|
+
}
|
|
738
748
|
async close() {
|
|
749
|
+
this.closeNotificationStream();
|
|
739
750
|
this.state = "disconnected";
|
|
740
751
|
this.sessionId = void 0;
|
|
741
752
|
this.log("StreamableHTTP transport closed");
|
|
@@ -774,6 +785,7 @@ var StreamableHttpTransport = class {
|
|
|
774
785
|
return { ...this.lastRequestHeaders };
|
|
775
786
|
}
|
|
776
787
|
async simulateDisconnect() {
|
|
788
|
+
this.closeNotificationStream();
|
|
777
789
|
this.state = "disconnected";
|
|
778
790
|
this.sessionId = void 0;
|
|
779
791
|
}
|
|
@@ -792,6 +804,72 @@ var StreamableHttpTransport = class {
|
|
|
792
804
|
// ═══════════════════════════════════════════════════════════════════
|
|
793
805
|
// PRIVATE HELPERS
|
|
794
806
|
// ═══════════════════════════════════════════════════════════════════
|
|
807
|
+
closeNotificationStream() {
|
|
808
|
+
clearTimeout(this.notificationStreamReopenTimer);
|
|
809
|
+
this.notificationStream?.abort();
|
|
810
|
+
this.notificationStream = void 0;
|
|
811
|
+
}
|
|
812
|
+
/** Opens the stream without holding up the caller past the request timeout; close() aborts it at any point. */
|
|
813
|
+
async connectNotificationStream() {
|
|
814
|
+
if (!this.sessionId || this.notificationStream) return;
|
|
815
|
+
const controller = new AbortController();
|
|
816
|
+
this.notificationStream = controller;
|
|
817
|
+
const { "Content-Type": _contentType, ...headers } = this.buildHeaders();
|
|
818
|
+
const established = fetch(this.mcpUrl(), {
|
|
819
|
+
method: "GET",
|
|
820
|
+
headers: { ...headers, Accept: "text/event-stream", "MCP-Protocol-Version": this.notificationProtocolVersion },
|
|
821
|
+
signal: controller.signal
|
|
822
|
+
}).then(
|
|
823
|
+
async (response) => {
|
|
824
|
+
const isEventStream = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
|
|
825
|
+
if (!response.ok || !response.body || !isEventStream) {
|
|
826
|
+
await response.body?.cancel();
|
|
827
|
+
this.log(`Server offers no notification stream (HTTP ${response.status})`);
|
|
828
|
+
this.releaseNotificationStream(controller);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
void this.readNotificationStream(controller, response.body.getReader());
|
|
832
|
+
},
|
|
833
|
+
(error) => {
|
|
834
|
+
this.log("Failed to open the notification stream:", error);
|
|
835
|
+
this.releaseNotificationStream(controller);
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
let establishTimer;
|
|
839
|
+
const establishDeadline = new Promise((resolve) => {
|
|
840
|
+
establishTimer = setTimeout(resolve, this.config.timeout);
|
|
841
|
+
});
|
|
842
|
+
await Promise.race([established, establishDeadline]);
|
|
843
|
+
clearTimeout(establishTimer);
|
|
844
|
+
}
|
|
845
|
+
releaseNotificationStream(controller) {
|
|
846
|
+
if (this.notificationStream === controller) this.notificationStream = void 0;
|
|
847
|
+
}
|
|
848
|
+
async readNotificationStream(controller, reader) {
|
|
849
|
+
const decoder = new TextDecoder();
|
|
850
|
+
let buffer = "";
|
|
851
|
+
try {
|
|
852
|
+
for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
|
|
853
|
+
const { completeEvents, remainder } = splitSSEEvents(buffer + decoder.decode(chunk.value, { stream: true }));
|
|
854
|
+
buffer = remainder;
|
|
855
|
+
for (const eventText of completeEvents) {
|
|
856
|
+
for (const event of this.parseSSEEvents(eventText, void 0).events) {
|
|
857
|
+
await this.handleSSEEvent(event);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
} catch (error) {
|
|
862
|
+
this.log("Notification stream ended:", error);
|
|
863
|
+
} finally {
|
|
864
|
+
reader.releaseLock();
|
|
865
|
+
}
|
|
866
|
+
if (controller.signal.aborted || this.notificationStream !== controller) return;
|
|
867
|
+
this.releaseNotificationStream(controller);
|
|
868
|
+
this.notificationStreamReopenTimer = setTimeout(
|
|
869
|
+
() => void this.connectNotificationStream(),
|
|
870
|
+
NOTIFICATION_STREAM_REOPEN_DELAY_MS
|
|
871
|
+
);
|
|
872
|
+
}
|
|
795
873
|
/**
|
|
796
874
|
* Handle SSE response with elicitation support.
|
|
797
875
|
*
|
|
@@ -929,6 +1007,13 @@ var StreamableHttpTransport = class {
|
|
|
929
1007
|
response: { jsonrpc: "2.0", id: null, result: void 0 }
|
|
930
1008
|
};
|
|
931
1009
|
}
|
|
1010
|
+
if ("method" in parsed && parsed.id === void 0) {
|
|
1011
|
+
this.notificationHandler?.(parsed);
|
|
1012
|
+
return {
|
|
1013
|
+
isFinal: false,
|
|
1014
|
+
response: { jsonrpc: "2.0", id: null, result: void 0 }
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
932
1017
|
if ("result" in parsed || "error" in parsed) {
|
|
933
1018
|
return { isFinal: true, response: parsed };
|
|
934
1019
|
}
|
|
@@ -1099,6 +1184,13 @@ var StreamableHttpTransport = class {
|
|
|
1099
1184
|
};
|
|
1100
1185
|
}
|
|
1101
1186
|
};
|
|
1187
|
+
function splitSSEEvents(buffer) {
|
|
1188
|
+
const endsWithCarriageReturn = buffer.endsWith("\r");
|
|
1189
|
+
const normalized = (endsWithCarriageReturn ? buffer.slice(0, -1) : buffer).replace(/\r\n?/g, "\n");
|
|
1190
|
+
const completeEvents = normalized.split("\n\n");
|
|
1191
|
+
const remainder = completeEvents.pop() ?? "";
|
|
1192
|
+
return { completeEvents, remainder: endsWithCarriageReturn ? `${remainder}\r` : remainder };
|
|
1193
|
+
}
|
|
1102
1194
|
|
|
1103
1195
|
// libs/testing/src/platform/platform-client-info.ts
|
|
1104
1196
|
var MCP_APPS_EXTENSION_KEY = "io.modelcontextprotocol/ui";
|
|
@@ -1235,7 +1327,8 @@ var McpTestClientBuilder = class {
|
|
|
1235
1327
|
return this;
|
|
1236
1328
|
}
|
|
1237
1329
|
/**
|
|
1238
|
-
* Set the MCP protocol version to request
|
|
1330
|
+
* Set the MCP protocol version to request (default '2025-06-18').
|
|
1331
|
+
* '2026-07-28' has no initialize handshake, so `build()` throws for it.
|
|
1239
1332
|
*/
|
|
1240
1333
|
withProtocolVersion(version) {
|
|
1241
1334
|
this.config.protocolVersion = version;
|
|
@@ -1331,6 +1424,7 @@ var McpTestClientBuilder = class {
|
|
|
1331
1424
|
// libs/testing/src/client/mcp-test-client.ts
|
|
1332
1425
|
var DEFAULT_TIMEOUT2 = 3e4;
|
|
1333
1426
|
var DEFAULT_PROTOCOL_VERSION = "2025-06-18";
|
|
1427
|
+
var UNSUPPORTED_PROTOCOL_VERSIONS = ["2026-07-28"];
|
|
1334
1428
|
var DEFAULT_CLIENT_INFO = {
|
|
1335
1429
|
name: "@frontmcp/testing",
|
|
1336
1430
|
version: "0.4.0"
|
|
@@ -1367,6 +1461,8 @@ var McpTestClient = class {
|
|
|
1367
1461
|
_traces = [];
|
|
1368
1462
|
_notifications = [];
|
|
1369
1463
|
_progressUpdates = [];
|
|
1464
|
+
collectingProgress = false;
|
|
1465
|
+
progressTokenCounter = 0;
|
|
1370
1466
|
// Interceptor chain
|
|
1371
1467
|
_interceptors;
|
|
1372
1468
|
// Elicitation handler for server→client elicit requests
|
|
@@ -1375,6 +1471,11 @@ var McpTestClient = class {
|
|
|
1375
1471
|
// CONSTRUCTOR & FACTORY
|
|
1376
1472
|
// ═══════════════════════════════════════════════════════════════════
|
|
1377
1473
|
constructor(config) {
|
|
1474
|
+
if (config.protocolVersion && UNSUPPORTED_PROTOCOL_VERSIONS.includes(config.protocolVersion)) {
|
|
1475
|
+
throw new Error(
|
|
1476
|
+
`McpTestClient does not support protocol version ${config.protocolVersion}: it has no initialize handshake. Use ${DEFAULT_PROTOCOL_VERSION} or earlier.`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1378
1479
|
this.config = {
|
|
1379
1480
|
baseUrl: config.baseUrl,
|
|
1380
1481
|
entryPath: config.entryPath,
|
|
@@ -1460,6 +1561,7 @@ var McpTestClient = class {
|
|
|
1460
1561
|
jsonrpc: "2.0",
|
|
1461
1562
|
method: "notifications/initialized"
|
|
1462
1563
|
});
|
|
1564
|
+
await this.transport.openNotificationStream?.(this.initResult.protocolVersion);
|
|
1463
1565
|
this.log("info", `Connected to ${this.initResult.serverInfo?.name ?? "MCP Server"}`);
|
|
1464
1566
|
return this.initResult;
|
|
1465
1567
|
}
|
|
@@ -1570,8 +1672,8 @@ var McpTestClient = class {
|
|
|
1570
1672
|
/**
|
|
1571
1673
|
* Call a tool by name with arguments
|
|
1572
1674
|
*/
|
|
1573
|
-
call: async (name, args) => {
|
|
1574
|
-
const response = await this.callTool(name, args);
|
|
1675
|
+
call: async (name, args, options = {}) => {
|
|
1676
|
+
const response = await this.callTool(name, args, this.progressTokenFor(options));
|
|
1575
1677
|
return this.wrapToolResult(response);
|
|
1576
1678
|
}
|
|
1577
1679
|
};
|
|
@@ -1713,6 +1815,7 @@ var McpTestClient = class {
|
|
|
1713
1815
|
* Collect progress notifications specifically
|
|
1714
1816
|
*/
|
|
1715
1817
|
collectProgress: () => {
|
|
1818
|
+
this.collectingProgress = true;
|
|
1716
1819
|
return new ProgressCollector(this._progressUpdates);
|
|
1717
1820
|
},
|
|
1718
1821
|
/**
|
|
@@ -2018,12 +2121,29 @@ var McpTestClient = class {
|
|
|
2018
2121
|
async listTools() {
|
|
2019
2122
|
return this.request("tools/list", {});
|
|
2020
2123
|
}
|
|
2021
|
-
async callTool(name, args) {
|
|
2124
|
+
async callTool(name, args, progressToken) {
|
|
2022
2125
|
return this.request("tools/call", {
|
|
2023
2126
|
name,
|
|
2024
|
-
arguments: args ?? {}
|
|
2127
|
+
arguments: args ?? {},
|
|
2128
|
+
...progressToken !== void 0 && { _meta: { progressToken } }
|
|
2025
2129
|
});
|
|
2026
2130
|
}
|
|
2131
|
+
progressTokenFor(options) {
|
|
2132
|
+
if (options.progressToken !== void 0) return options.progressToken;
|
|
2133
|
+
return this.collectingProgress ? `progress-${++this.progressTokenCounter}` : void 0;
|
|
2134
|
+
}
|
|
2135
|
+
recordNotification = (notification) => {
|
|
2136
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
2137
|
+
this._notifications.push({ method: notification.method, params: notification.params, timestamp });
|
|
2138
|
+
if (notification.method !== "notifications/progress") return;
|
|
2139
|
+
const { progress, total, progressToken } = notification.params ?? {};
|
|
2140
|
+
this._progressUpdates.push({
|
|
2141
|
+
progress: Number(progress),
|
|
2142
|
+
total: typeof total === "number" ? total : void 0,
|
|
2143
|
+
progressToken: typeof progressToken === "string" || typeof progressToken === "number" ? progressToken : void 0,
|
|
2144
|
+
timestamp
|
|
2145
|
+
});
|
|
2146
|
+
};
|
|
2027
2147
|
async listResources() {
|
|
2028
2148
|
return this.request("resources/list", {});
|
|
2029
2149
|
}
|
|
@@ -2070,7 +2190,8 @@ var McpTestClient = class {
|
|
|
2070
2190
|
debug: this.config.debug,
|
|
2071
2191
|
interceptors: this._interceptors,
|
|
2072
2192
|
clientInfo: this.config.clientInfo,
|
|
2073
|
-
elicitationHandler: this._elicitationHandler
|
|
2193
|
+
elicitationHandler: this._elicitationHandler,
|
|
2194
|
+
notificationHandler: this.recordNotification
|
|
2074
2195
|
});
|
|
2075
2196
|
case "sse":
|
|
2076
2197
|
throw new Error("SSE transport not yet implemented");
|
package/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
export { McpTestClient } from './client/mcp-test-client';
|
|
42
42
|
export { McpTestClientBuilder } from './client/mcp-test-client.builder';
|
|
43
|
-
export type { McpTestClientConfig, McpResponse, McpErrorInfo, TestTransportType, TestAuthConfig, ToolResultWrapper, ResourceContentWrapper, PromptResultWrapper, LogEntry, LogLevel, RequestTrace, NotificationEntry, ProgressUpdate, SessionInfo, AuthState, } from './client/mcp-test-client.types';
|
|
43
|
+
export type { McpTestClientConfig, McpResponse, McpErrorInfo, TestTransportType, TestAuthConfig, ToolResultWrapper, ResourceContentWrapper, PromptResultWrapper, LogEntry, LogLevel, RequestTrace, NotificationEntry, ProgressUpdate, ToolCallOptions, SessionInfo, AuthState, } from './client/mcp-test-client.types';
|
|
44
44
|
export type { McpTransport, TransportConfig, TransportState } from './transport/transport.interface';
|
|
45
45
|
export { StreamableHttpTransport } from './transport/streamable-http.transport';
|
|
46
46
|
export { TestTokenFactory } from './auth/token-factory';
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAMH,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACd,WAAW,EACX,SAAS,GACV,MAAM,gCAAgC,CAAC;AAMxC,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACrG,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAMhF,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC1G,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAMhH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAM9E,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,wBAAwB,EACxB,cAAc,EACd,YAAY,EACZ,OAAO,EACP,cAAc,EACd,WAAW,GACZ,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EACL,eAAe,EACf,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,GACf,MAAM,gBAAgB,CAAC;AAMxB,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,2BAA2B,EAC3B,iBAAiB,EACjB,eAAe,EACf,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,MAAM,EAEN,cAAc,EACd,eAAe,GAChB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,YAAY,EACV,UAAU,EACV,YAAY,EACZ,WAAW,EACX,aAAa,EACb,MAAM,EACN,gBAAgB,EAChB,QAAQ,GACT,MAAM,YAAY,CAAC;AAQpB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAMlC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C,OAAO,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEzG,YAAY,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,EAC1B,yBAAyB,EACzB,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,UAAU,EACV,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAMvB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAErD,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAMhD,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE1F,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,cAAc,EACd,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,YAAY,CAAC;AAMpB,OAAO,EAEL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EAEtB,yBAAyB,EACzB,wBAAwB,EAExB,oCAAoC,EACpC,mCAAmC,EACnC,qCAAqC,EACrC,oCAAoC,EACpC,qCAAqC,EACrC,oCAAoC,GACrC,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EAEL,QAAQ,EAGR,gBAAgB,EAChB,aAAa,EACb,OAAO,EACP,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,cAAc,EAGd,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAGlB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EAGvB,aAAa,EACb,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAGhB,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EAGrB,eAAe,EACf,WAAW,EACX,qBAAqB,GACtB,MAAM,QAAQ,CAAC;AAMhB,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEtF,YAAY,EAEV,aAAa,EACb,UAAU,EACV,YAAY,EAGZ,eAAe,EACf,SAAS,EACT,cAAc,EAGd,oBAAoB,EACpB,mBAAmB,EAGnB,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAGhB,eAAe,EACf,cAAc,EACd,UAAU,EAGV,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,oBAAoB,GACrB,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -462,6 +462,7 @@ var interceptors = {
|
|
|
462
462
|
|
|
463
463
|
// libs/testing/src/transport/streamable-http.transport.ts
|
|
464
464
|
var DEFAULT_TIMEOUT = 3e4;
|
|
465
|
+
var NOTIFICATION_STREAM_REOPEN_DELAY_MS = 250;
|
|
465
466
|
var StreamableHttpTransport = class {
|
|
466
467
|
config;
|
|
467
468
|
state = "disconnected";
|
|
@@ -473,6 +474,10 @@ var StreamableHttpTransport = class {
|
|
|
473
474
|
interceptors;
|
|
474
475
|
publicMode;
|
|
475
476
|
elicitationHandler;
|
|
477
|
+
notificationHandler;
|
|
478
|
+
notificationStream;
|
|
479
|
+
notificationProtocolVersion = "";
|
|
480
|
+
notificationStreamReopenTimer;
|
|
476
481
|
constructor(config) {
|
|
477
482
|
this.config = {
|
|
478
483
|
baseUrl: config.baseUrl.replace(/\/$/, ""),
|
|
@@ -489,6 +494,7 @@ var StreamableHttpTransport = class {
|
|
|
489
494
|
this.interceptors = config.interceptors;
|
|
490
495
|
this.publicMode = config.publicMode ?? false;
|
|
491
496
|
this.elicitationHandler = config.elicitationHandler;
|
|
497
|
+
this.notificationHandler = config.notificationHandler;
|
|
492
498
|
}
|
|
493
499
|
async connect() {
|
|
494
500
|
this.state = "connecting";
|
|
@@ -708,7 +714,12 @@ var StreamableHttpTransport = class {
|
|
|
708
714
|
};
|
|
709
715
|
}
|
|
710
716
|
}
|
|
717
|
+
async openNotificationStream(protocolVersion) {
|
|
718
|
+
this.notificationProtocolVersion = protocolVersion;
|
|
719
|
+
await this.connectNotificationStream();
|
|
720
|
+
}
|
|
711
721
|
async close() {
|
|
722
|
+
this.closeNotificationStream();
|
|
712
723
|
this.state = "disconnected";
|
|
713
724
|
this.sessionId = void 0;
|
|
714
725
|
this.log("StreamableHTTP transport closed");
|
|
@@ -747,6 +758,7 @@ var StreamableHttpTransport = class {
|
|
|
747
758
|
return { ...this.lastRequestHeaders };
|
|
748
759
|
}
|
|
749
760
|
async simulateDisconnect() {
|
|
761
|
+
this.closeNotificationStream();
|
|
750
762
|
this.state = "disconnected";
|
|
751
763
|
this.sessionId = void 0;
|
|
752
764
|
}
|
|
@@ -765,6 +777,72 @@ var StreamableHttpTransport = class {
|
|
|
765
777
|
// ═══════════════════════════════════════════════════════════════════
|
|
766
778
|
// PRIVATE HELPERS
|
|
767
779
|
// ═══════════════════════════════════════════════════════════════════
|
|
780
|
+
closeNotificationStream() {
|
|
781
|
+
clearTimeout(this.notificationStreamReopenTimer);
|
|
782
|
+
this.notificationStream?.abort();
|
|
783
|
+
this.notificationStream = void 0;
|
|
784
|
+
}
|
|
785
|
+
/** Opens the stream without holding up the caller past the request timeout; close() aborts it at any point. */
|
|
786
|
+
async connectNotificationStream() {
|
|
787
|
+
if (!this.sessionId || this.notificationStream) return;
|
|
788
|
+
const controller = new AbortController();
|
|
789
|
+
this.notificationStream = controller;
|
|
790
|
+
const { "Content-Type": _contentType, ...headers } = this.buildHeaders();
|
|
791
|
+
const established = fetch(this.mcpUrl(), {
|
|
792
|
+
method: "GET",
|
|
793
|
+
headers: { ...headers, Accept: "text/event-stream", "MCP-Protocol-Version": this.notificationProtocolVersion },
|
|
794
|
+
signal: controller.signal
|
|
795
|
+
}).then(
|
|
796
|
+
async (response) => {
|
|
797
|
+
const isEventStream = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
|
|
798
|
+
if (!response.ok || !response.body || !isEventStream) {
|
|
799
|
+
await response.body?.cancel();
|
|
800
|
+
this.log(`Server offers no notification stream (HTTP ${response.status})`);
|
|
801
|
+
this.releaseNotificationStream(controller);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
void this.readNotificationStream(controller, response.body.getReader());
|
|
805
|
+
},
|
|
806
|
+
(error) => {
|
|
807
|
+
this.log("Failed to open the notification stream:", error);
|
|
808
|
+
this.releaseNotificationStream(controller);
|
|
809
|
+
}
|
|
810
|
+
);
|
|
811
|
+
let establishTimer;
|
|
812
|
+
const establishDeadline = new Promise((resolve) => {
|
|
813
|
+
establishTimer = setTimeout(resolve, this.config.timeout);
|
|
814
|
+
});
|
|
815
|
+
await Promise.race([established, establishDeadline]);
|
|
816
|
+
clearTimeout(establishTimer);
|
|
817
|
+
}
|
|
818
|
+
releaseNotificationStream(controller) {
|
|
819
|
+
if (this.notificationStream === controller) this.notificationStream = void 0;
|
|
820
|
+
}
|
|
821
|
+
async readNotificationStream(controller, reader) {
|
|
822
|
+
const decoder = new TextDecoder();
|
|
823
|
+
let buffer = "";
|
|
824
|
+
try {
|
|
825
|
+
for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
|
|
826
|
+
const { completeEvents, remainder } = splitSSEEvents(buffer + decoder.decode(chunk.value, { stream: true }));
|
|
827
|
+
buffer = remainder;
|
|
828
|
+
for (const eventText of completeEvents) {
|
|
829
|
+
for (const event of this.parseSSEEvents(eventText, void 0).events) {
|
|
830
|
+
await this.handleSSEEvent(event);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
} catch (error) {
|
|
835
|
+
this.log("Notification stream ended:", error);
|
|
836
|
+
} finally {
|
|
837
|
+
reader.releaseLock();
|
|
838
|
+
}
|
|
839
|
+
if (controller.signal.aborted || this.notificationStream !== controller) return;
|
|
840
|
+
this.releaseNotificationStream(controller);
|
|
841
|
+
this.notificationStreamReopenTimer = setTimeout(
|
|
842
|
+
() => void this.connectNotificationStream(),
|
|
843
|
+
NOTIFICATION_STREAM_REOPEN_DELAY_MS
|
|
844
|
+
);
|
|
845
|
+
}
|
|
768
846
|
/**
|
|
769
847
|
* Handle SSE response with elicitation support.
|
|
770
848
|
*
|
|
@@ -902,6 +980,13 @@ var StreamableHttpTransport = class {
|
|
|
902
980
|
response: { jsonrpc: "2.0", id: null, result: void 0 }
|
|
903
981
|
};
|
|
904
982
|
}
|
|
983
|
+
if ("method" in parsed && parsed.id === void 0) {
|
|
984
|
+
this.notificationHandler?.(parsed);
|
|
985
|
+
return {
|
|
986
|
+
isFinal: false,
|
|
987
|
+
response: { jsonrpc: "2.0", id: null, result: void 0 }
|
|
988
|
+
};
|
|
989
|
+
}
|
|
905
990
|
if ("result" in parsed || "error" in parsed) {
|
|
906
991
|
return { isFinal: true, response: parsed };
|
|
907
992
|
}
|
|
@@ -1072,6 +1157,13 @@ var StreamableHttpTransport = class {
|
|
|
1072
1157
|
};
|
|
1073
1158
|
}
|
|
1074
1159
|
};
|
|
1160
|
+
function splitSSEEvents(buffer) {
|
|
1161
|
+
const endsWithCarriageReturn = buffer.endsWith("\r");
|
|
1162
|
+
const normalized = (endsWithCarriageReturn ? buffer.slice(0, -1) : buffer).replace(/\r\n?/g, "\n");
|
|
1163
|
+
const completeEvents = normalized.split("\n\n");
|
|
1164
|
+
const remainder = completeEvents.pop() ?? "";
|
|
1165
|
+
return { completeEvents, remainder: endsWithCarriageReturn ? `${remainder}\r` : remainder };
|
|
1166
|
+
}
|
|
1075
1167
|
|
|
1076
1168
|
// libs/testing/src/platform/platform-client-info.ts
|
|
1077
1169
|
var MCP_APPS_EXTENSION_KEY = "io.modelcontextprotocol/ui";
|
|
@@ -1227,7 +1319,8 @@ var McpTestClientBuilder = class {
|
|
|
1227
1319
|
return this;
|
|
1228
1320
|
}
|
|
1229
1321
|
/**
|
|
1230
|
-
* Set the MCP protocol version to request
|
|
1322
|
+
* Set the MCP protocol version to request (default '2025-06-18').
|
|
1323
|
+
* '2026-07-28' has no initialize handshake, so `build()` throws for it.
|
|
1231
1324
|
*/
|
|
1232
1325
|
withProtocolVersion(version) {
|
|
1233
1326
|
this.config.protocolVersion = version;
|
|
@@ -1323,6 +1416,7 @@ var McpTestClientBuilder = class {
|
|
|
1323
1416
|
// libs/testing/src/client/mcp-test-client.ts
|
|
1324
1417
|
var DEFAULT_TIMEOUT2 = 3e4;
|
|
1325
1418
|
var DEFAULT_PROTOCOL_VERSION = "2025-06-18";
|
|
1419
|
+
var UNSUPPORTED_PROTOCOL_VERSIONS = ["2026-07-28"];
|
|
1326
1420
|
var DEFAULT_CLIENT_INFO = {
|
|
1327
1421
|
name: "@frontmcp/testing",
|
|
1328
1422
|
version: "0.4.0"
|
|
@@ -1359,6 +1453,8 @@ var McpTestClient = class {
|
|
|
1359
1453
|
_traces = [];
|
|
1360
1454
|
_notifications = [];
|
|
1361
1455
|
_progressUpdates = [];
|
|
1456
|
+
collectingProgress = false;
|
|
1457
|
+
progressTokenCounter = 0;
|
|
1362
1458
|
// Interceptor chain
|
|
1363
1459
|
_interceptors;
|
|
1364
1460
|
// Elicitation handler for server→client elicit requests
|
|
@@ -1367,6 +1463,11 @@ var McpTestClient = class {
|
|
|
1367
1463
|
// CONSTRUCTOR & FACTORY
|
|
1368
1464
|
// ═══════════════════════════════════════════════════════════════════
|
|
1369
1465
|
constructor(config) {
|
|
1466
|
+
if (config.protocolVersion && UNSUPPORTED_PROTOCOL_VERSIONS.includes(config.protocolVersion)) {
|
|
1467
|
+
throw new Error(
|
|
1468
|
+
`McpTestClient does not support protocol version ${config.protocolVersion}: it has no initialize handshake. Use ${DEFAULT_PROTOCOL_VERSION} or earlier.`
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1370
1471
|
this.config = {
|
|
1371
1472
|
baseUrl: config.baseUrl,
|
|
1372
1473
|
entryPath: config.entryPath,
|
|
@@ -1452,6 +1553,7 @@ var McpTestClient = class {
|
|
|
1452
1553
|
jsonrpc: "2.0",
|
|
1453
1554
|
method: "notifications/initialized"
|
|
1454
1555
|
});
|
|
1556
|
+
await this.transport.openNotificationStream?.(this.initResult.protocolVersion);
|
|
1455
1557
|
this.log("info", `Connected to ${this.initResult.serverInfo?.name ?? "MCP Server"}`);
|
|
1456
1558
|
return this.initResult;
|
|
1457
1559
|
}
|
|
@@ -1562,8 +1664,8 @@ var McpTestClient = class {
|
|
|
1562
1664
|
/**
|
|
1563
1665
|
* Call a tool by name with arguments
|
|
1564
1666
|
*/
|
|
1565
|
-
call: async (name, args) => {
|
|
1566
|
-
const response = await this.callTool(name, args);
|
|
1667
|
+
call: async (name, args, options = {}) => {
|
|
1668
|
+
const response = await this.callTool(name, args, this.progressTokenFor(options));
|
|
1567
1669
|
return this.wrapToolResult(response);
|
|
1568
1670
|
}
|
|
1569
1671
|
};
|
|
@@ -1705,6 +1807,7 @@ var McpTestClient = class {
|
|
|
1705
1807
|
* Collect progress notifications specifically
|
|
1706
1808
|
*/
|
|
1707
1809
|
collectProgress: () => {
|
|
1810
|
+
this.collectingProgress = true;
|
|
1708
1811
|
return new ProgressCollector(this._progressUpdates);
|
|
1709
1812
|
},
|
|
1710
1813
|
/**
|
|
@@ -2010,12 +2113,29 @@ var McpTestClient = class {
|
|
|
2010
2113
|
async listTools() {
|
|
2011
2114
|
return this.request("tools/list", {});
|
|
2012
2115
|
}
|
|
2013
|
-
async callTool(name, args) {
|
|
2116
|
+
async callTool(name, args, progressToken) {
|
|
2014
2117
|
return this.request("tools/call", {
|
|
2015
2118
|
name,
|
|
2016
|
-
arguments: args ?? {}
|
|
2119
|
+
arguments: args ?? {},
|
|
2120
|
+
...progressToken !== void 0 && { _meta: { progressToken } }
|
|
2017
2121
|
});
|
|
2018
2122
|
}
|
|
2123
|
+
progressTokenFor(options) {
|
|
2124
|
+
if (options.progressToken !== void 0) return options.progressToken;
|
|
2125
|
+
return this.collectingProgress ? `progress-${++this.progressTokenCounter}` : void 0;
|
|
2126
|
+
}
|
|
2127
|
+
recordNotification = (notification) => {
|
|
2128
|
+
const timestamp = /* @__PURE__ */ new Date();
|
|
2129
|
+
this._notifications.push({ method: notification.method, params: notification.params, timestamp });
|
|
2130
|
+
if (notification.method !== "notifications/progress") return;
|
|
2131
|
+
const { progress, total, progressToken } = notification.params ?? {};
|
|
2132
|
+
this._progressUpdates.push({
|
|
2133
|
+
progress: Number(progress),
|
|
2134
|
+
total: typeof total === "number" ? total : void 0,
|
|
2135
|
+
progressToken: typeof progressToken === "string" || typeof progressToken === "number" ? progressToken : void 0,
|
|
2136
|
+
timestamp
|
|
2137
|
+
});
|
|
2138
|
+
};
|
|
2019
2139
|
async listResources() {
|
|
2020
2140
|
return this.request("resources/list", {});
|
|
2021
2141
|
}
|
|
@@ -2062,7 +2182,8 @@ var McpTestClient = class {
|
|
|
2062
2182
|
debug: this.config.debug,
|
|
2063
2183
|
interceptors: this._interceptors,
|
|
2064
2184
|
clientInfo: this.config.clientInfo,
|
|
2065
|
-
elicitationHandler: this._elicitationHandler
|
|
2185
|
+
elicitationHandler: this._elicitationHandler,
|
|
2186
|
+
notificationHandler: this.recordNotification
|
|
2066
2187
|
});
|
|
2067
2188
|
case "sse":
|
|
2068
2189
|
throw new Error("SSE transport not yet implemented");
|
|
@@ -5598,18 +5719,17 @@ var toBeError = function(received, expectedCode) {
|
|
|
5598
5719
|
message: () => `Expected a result wrapper object with isError property`
|
|
5599
5720
|
};
|
|
5600
5721
|
}
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
}
|
|
5722
|
+
const receivedCode = typeof expectedCode === "string" ? result.raw?._meta?.["code"] : result.error?.code;
|
|
5723
|
+
const codeMatches = expectedCode === void 0 || receivedCode === expectedCode;
|
|
5724
|
+
const pass = result.isError && codeMatches;
|
|
5605
5725
|
return {
|
|
5606
5726
|
pass,
|
|
5607
5727
|
message: () => {
|
|
5608
5728
|
if (!result.isError) {
|
|
5609
5729
|
return "Expected result to be an error, but it was successful";
|
|
5610
5730
|
}
|
|
5611
|
-
if (
|
|
5612
|
-
return `Expected error code ${expectedCode}, but got ${
|
|
5731
|
+
if (!codeMatches) {
|
|
5732
|
+
return `Expected error code ${expectedCode}, but got ${String(receivedCode)}`;
|
|
5613
5733
|
}
|
|
5614
5734
|
return "Expected result not to be an error";
|
|
5615
5735
|
}
|
package/matchers/index.js
CHANGED
|
@@ -389,18 +389,17 @@ var toBeError = function(received, expectedCode) {
|
|
|
389
389
|
message: () => `Expected a result wrapper object with isError property`
|
|
390
390
|
};
|
|
391
391
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
392
|
+
const receivedCode = typeof expectedCode === "string" ? result.raw?._meta?.["code"] : result.error?.code;
|
|
393
|
+
const codeMatches = expectedCode === void 0 || receivedCode === expectedCode;
|
|
394
|
+
const pass = result.isError && codeMatches;
|
|
396
395
|
return {
|
|
397
396
|
pass,
|
|
398
397
|
message: () => {
|
|
399
398
|
if (!result.isError) {
|
|
400
399
|
return "Expected result to be an error, but it was successful";
|
|
401
400
|
}
|
|
402
|
-
if (
|
|
403
|
-
return `Expected error code ${expectedCode}, but got ${
|
|
401
|
+
if (!codeMatches) {
|
|
402
|
+
return `Expected error code ${expectedCode}, but got ${String(receivedCode)}`;
|
|
404
403
|
}
|
|
405
404
|
return "Expected result not to be an error";
|
|
406
405
|
}
|
|
@@ -32,16 +32,17 @@ export interface McpMatchers<R = unknown> {
|
|
|
32
32
|
toBeSuccessful(): R;
|
|
33
33
|
/**
|
|
34
34
|
* Check if result is an error, optionally with a specific error code
|
|
35
|
-
* @param expectedCode -
|
|
35
|
+
* @param expectedCode - A JSON-RPC error code (number), or a tool error code (string) matched against `_meta.code`
|
|
36
36
|
*
|
|
37
37
|
* @example
|
|
38
38
|
* ```typescript
|
|
39
39
|
* const result = await mcp.tools.call('unknown-tool', {});
|
|
40
40
|
* expect(result).toBeError();
|
|
41
41
|
* expect(result).toBeError(-32601); // Method not found
|
|
42
|
+
* expect(await mcp.tools.call('add', { a: 5 })).toBeError('INVALID_INPUT');
|
|
42
43
|
* ```
|
|
43
44
|
*/
|
|
44
|
-
toBeError(expectedCode?: number): R;
|
|
45
|
+
toBeError(expectedCode?: number | string): R;
|
|
45
46
|
/**
|
|
46
47
|
* Check if tool result has text content, optionally containing specific text
|
|
47
48
|
* @param expectedText - Optional text that should be contained in the result
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matcher-types.d.ts","sourceRoot":"","sources":["../../src/matchers/matcher-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IAKtC;;;;;;;;;OASG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IAEnC;;;;;;;;OAQG;IACH,cAAc,IAAI,CAAC,CAAC;IAEpB
|
|
1
|
+
{"version":3,"file":"matcher-types.d.ts","sourceRoot":"","sources":["../../src/matchers/matcher-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IAKtC;;;;;;;;;OASG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IAEnC;;;;;;;;OAQG;IACH,cAAc,IAAI,CAAC,CAAC;IAEpB;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC;IAE7C;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IAE5C;;;;;;;;OAQG;IACH,kBAAkB,IAAI,CAAC,CAAC;IAExB;;;;;;;;OAQG;IACH,qBAAqB,IAAI,CAAC,CAAC;IAM3B;;;;;;;;;OASG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IAElC;;;;;;;;;OASG;IACH,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,CAAC,CAAC;IAElD;;;;;;;;;OASG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IAMpC;;;;;;;;;OASG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC;IAEjC;;;;;;;;;OASG;IACH,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC;IAMjC;;;;;;;;OAQG;IACH,gBAAgB,IAAI,CAAC,CAAC;IAEtB;;;;;;;;OAQG;IACH,YAAY,IAAI,CAAC,CAAC;IAElB;;;;;;;;OAQG;IACH,WAAW,IAAI,CAAC,CAAC;IAEjB;;;;;;;;;OASG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC;IAMjC;;;;;;;;;OASG;IACH,kBAAkB,IAAI,CAAC,CAAC;IAExB;;;;;;;;;OASG;IACH,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IAErC;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC;IAE/C;;;;;;;OAOG;IACH,WAAW,IAAI,CAAC,CAAC;IAEjB;;;;;;;OAOG;IACH,oBAAoB,IAAI,CAAC,CAAC;IAE1B;;;;;;;;OAQG;IACH,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,CAAC,CAAC;IAErC;;;;;;;;;OASG;IACH,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC;IAE3C;;;;;;;OAOG;IACH,yBAAyB,IAAI,CAAC,CAAC;CAChC;AAQD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,IAAI,CAAC;QAEb,UAAU,QAAQ,CAAC,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,CAAC;SAAG;QAG/C,UAAU,MAAO,SAAQ,WAAW,CAAC,IAAI,CAAC;SAAG;QAG7C,UAAU,yBAA0B,SAAQ,WAAW,CAAC,IAAI,CAAC;SAAG;KACjE;CACF;AAID,OAAO,EAAE,CAAC"}
|
|
@@ -38,7 +38,7 @@ export declare const mcpMatchers: {
|
|
|
38
38
|
toHavePlatformHtml: MatcherFunction<[platform: import("..").TestPlatformType]>;
|
|
39
39
|
toContainTool: MatcherFunction<[toolName: string]>;
|
|
40
40
|
toBeSuccessful: MatcherFunction<[]>;
|
|
41
|
-
toBeError: MatcherFunction<[expectedCode?: number | undefined]>;
|
|
41
|
+
toBeError: MatcherFunction<[expectedCode?: string | number | undefined]>;
|
|
42
42
|
toHaveTextContent: MatcherFunction<[expectedText?: string | undefined]>;
|
|
43
43
|
toHaveImageContent: MatcherFunction<[]>;
|
|
44
44
|
toHaveResourceContent: MatcherFunction<[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-matchers.d.ts","sourceRoot":"","sources":["../../src/matchers/mcp-matchers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-matchers.d.ts","sourceRoot":"","sources":["../../src/matchers/mcp-matchers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AA6a9C;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BvB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/testing",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.1",
|
|
4
4
|
"description": "E2E testing framework for FrontMCP servers - MCP client, auth mocks, Playwright integration",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -87,9 +87,9 @@
|
|
|
87
87
|
"./esm": null
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"@frontmcp/lazy-zod": "1.8.
|
|
91
|
-
"@frontmcp/sdk": "1.8.
|
|
92
|
-
"@frontmcp/ui": "1.8.
|
|
90
|
+
"@frontmcp/lazy-zod": "1.8.1",
|
|
91
|
+
"@frontmcp/sdk": "1.8.1",
|
|
92
|
+
"@frontmcp/ui": "1.8.1",
|
|
93
93
|
"@jest/globals": "^29.0.0 || ^30.0.0",
|
|
94
94
|
"@playwright/test": "^1.40.0",
|
|
95
95
|
"jest": "^29.0.0 || ^30.0.0"
|
|
@@ -112,8 +112,8 @@
|
|
|
112
112
|
"node": ">=24.0.0"
|
|
113
113
|
},
|
|
114
114
|
"dependencies": {
|
|
115
|
-
"@frontmcp/protocol": "1.8.
|
|
116
|
-
"@frontmcp/utils": "1.8.
|
|
115
|
+
"@frontmcp/protocol": "1.8.1",
|
|
116
|
+
"@frontmcp/utils": "1.8.1",
|
|
117
117
|
"jose": "^6.0.11",
|
|
118
118
|
"tslib": "^2.3.0"
|
|
119
119
|
},
|