@gleanwork/mcp-server-tester 1.1.1 → 2.0.0-beta.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 +48 -0
- package/dist/cli/index.js +13766 -2790
- package/dist/fixtures/mcp.d.ts +74 -59
- package/dist/fixtures/mcp.js +161 -27
- package/dist/fixtures/mcp.js.map +1 -1
- package/dist/{index-BlqLvjnP.d.cts → index-aX_tCfTD.d.cts} +3787 -2736
- package/dist/{index-BlqLvjnP.d.ts → index-aX_tCfTD.d.ts} +3787 -2736
- package/dist/index.cjs +5792 -421
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -5
- package/dist/index.d.ts +175 -5
- package/dist/index.js +5732 -405
- package/dist/index.js.map +1 -1
- package/dist/reporters/mcpReporter.cjs +1 -0
- package/dist/reporters/mcpReporter.cjs.map +1 -1
- package/dist/reporters/mcpReporter.d.cts +80 -65
- package/dist/reporters/mcpReporter.d.ts +80 -65
- package/dist/reporters/mcpReporter.js +1 -0
- package/dist/reporters/mcpReporter.js.map +1 -1
- package/dist/reporters/ui-dist/app.js +18 -17
- package/dist/reporters/ui-dist/styles.css +1 -1
- package/dist/types/index.d.cts +3 -3
- package/dist/types/index.d.ts +3 -3
- package/package.json +8 -1
package/dist/fixtures/mcp.d.ts
CHANGED
|
@@ -216,6 +216,74 @@ type RubricSpec = BuiltInRubric | {
|
|
|
216
216
|
/** Valid LLM judge provider kinds. */
|
|
217
217
|
type ProviderKind = 'anthropic' | 'vertex-anthropic' | 'anthropic-agent-sdk' | 'openai' | 'google';
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* High-level API for interacting with MCP servers in tests
|
|
221
|
+
*
|
|
222
|
+
* This interface wraps the raw MCP Client with test-friendly methods
|
|
223
|
+
*/
|
|
224
|
+
interface MCPFixtureApi {
|
|
225
|
+
/**
|
|
226
|
+
* The underlying MCP client (for advanced usage)
|
|
227
|
+
*/
|
|
228
|
+
client: Client;
|
|
229
|
+
/**
|
|
230
|
+
* Authentication type used for this test session
|
|
231
|
+
*/
|
|
232
|
+
authType: AuthType;
|
|
233
|
+
/**
|
|
234
|
+
* Playwright project name for this test session
|
|
235
|
+
*/
|
|
236
|
+
project?: string;
|
|
237
|
+
/**
|
|
238
|
+
* Lists all available tools from the MCP server
|
|
239
|
+
*
|
|
240
|
+
* @returns Array of tool definitions
|
|
241
|
+
*/
|
|
242
|
+
listTools(): Promise<Array<Tool>>;
|
|
243
|
+
/**
|
|
244
|
+
* Calls a tool on the MCP server
|
|
245
|
+
*
|
|
246
|
+
* @param name - Tool name
|
|
247
|
+
* @param args - Tool arguments
|
|
248
|
+
* @returns Tool call result
|
|
249
|
+
*/
|
|
250
|
+
callTool<TArgs extends Record<string, unknown> = Record<string, unknown>>(name: string, args: TArgs): Promise<CallToolResult>;
|
|
251
|
+
/**
|
|
252
|
+
* Gets information about the connected server
|
|
253
|
+
*/
|
|
254
|
+
getServerInfo(): {
|
|
255
|
+
name?: string;
|
|
256
|
+
version?: string;
|
|
257
|
+
} | null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Canonical type definitions for @gleanwork/mcp-server-tester
|
|
262
|
+
*
|
|
263
|
+
* This module is the single source of truth for shared types.
|
|
264
|
+
* All other modules should import from here rather than defining their own.
|
|
265
|
+
*
|
|
266
|
+
* @packageDocumentation
|
|
267
|
+
*/
|
|
268
|
+
/**
|
|
269
|
+
* Authentication type for MCP connections
|
|
270
|
+
*
|
|
271
|
+
* - 'oauth': Interactive OAuth 2.1 with PKCE (browser-based authentication)
|
|
272
|
+
* - 'api-token': Static API token (e.g., from a dashboard or environment variable)
|
|
273
|
+
* - 'none': No authentication
|
|
274
|
+
*/
|
|
275
|
+
type AuthType = 'oauth' | 'api-token' | 'none';
|
|
276
|
+
|
|
277
|
+
interface HostEvent {
|
|
278
|
+
kind: 'tool_call' | 'skill' | 'command' | 'subagent';
|
|
279
|
+
source: 'mcp' | 'host';
|
|
280
|
+
name: string;
|
|
281
|
+
server?: string;
|
|
282
|
+
arguments?: Record<string, unknown>;
|
|
283
|
+
output?: string;
|
|
284
|
+
id?: string;
|
|
285
|
+
}
|
|
286
|
+
|
|
219
287
|
/**
|
|
220
288
|
* Tool call validators for mcp_host simulation results.
|
|
221
289
|
*
|
|
@@ -226,6 +294,9 @@ type ProviderKind = 'anthropic' | 'vertex-anthropic' | 'anthropic-agent-sdk' | '
|
|
|
226
294
|
interface ToolCallExpectation {
|
|
227
295
|
calls: Array<{
|
|
228
296
|
name: string;
|
|
297
|
+
kind?: HostEvent['kind'];
|
|
298
|
+
source?: HostEvent['source'];
|
|
299
|
+
server?: string;
|
|
229
300
|
arguments?: Record<string, unknown>;
|
|
230
301
|
required?: boolean;
|
|
231
302
|
}>;
|
|
@@ -248,6 +319,8 @@ interface ToolCallCountOptions {
|
|
|
248
319
|
* Options for the LLM judge matcher
|
|
249
320
|
*/
|
|
250
321
|
interface JudgeMatcherOptions {
|
|
322
|
+
/** Plugin options, parsed by the registered judge's schema. */
|
|
323
|
+
options?: Record<string, unknown>;
|
|
251
324
|
/** Reference response to compare against */
|
|
252
325
|
reference?: unknown;
|
|
253
326
|
/** Score threshold for passing (default: 0.7) */
|
|
@@ -261,7 +334,7 @@ interface JudgeMatcherOptions {
|
|
|
261
334
|
/**
|
|
262
335
|
* Name of a registered custom judge executor.
|
|
263
336
|
* When set, the named judge handles the entire evaluation pipeline
|
|
264
|
-
* and its
|
|
337
|
+
* and its score is compared with passingThreshold.
|
|
265
338
|
*/
|
|
266
339
|
judge?: string;
|
|
267
340
|
}
|
|
@@ -615,64 +688,6 @@ declare const expect: playwright_test.Expect<{
|
|
|
615
688
|
toHaveToolCallCount: typeof toHaveToolCallCount;
|
|
616
689
|
}>;
|
|
617
690
|
|
|
618
|
-
/**
|
|
619
|
-
* Canonical type definitions for @gleanwork/mcp-server-tester
|
|
620
|
-
*
|
|
621
|
-
* This module is the single source of truth for shared types.
|
|
622
|
-
* All other modules should import from here rather than defining their own.
|
|
623
|
-
*
|
|
624
|
-
* @packageDocumentation
|
|
625
|
-
*/
|
|
626
|
-
/**
|
|
627
|
-
* Authentication type for MCP connections
|
|
628
|
-
*
|
|
629
|
-
* - 'oauth': Interactive OAuth 2.1 with PKCE (browser-based authentication)
|
|
630
|
-
* - 'api-token': Static API token (e.g., from a dashboard or environment variable)
|
|
631
|
-
* - 'none': No authentication
|
|
632
|
-
*/
|
|
633
|
-
type AuthType = 'oauth' | 'api-token' | 'none';
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* High-level API for interacting with MCP servers in tests
|
|
637
|
-
*
|
|
638
|
-
* This interface wraps the raw MCP Client with test-friendly methods
|
|
639
|
-
*/
|
|
640
|
-
interface MCPFixtureApi {
|
|
641
|
-
/**
|
|
642
|
-
* The underlying MCP client (for advanced usage)
|
|
643
|
-
*/
|
|
644
|
-
client: Client;
|
|
645
|
-
/**
|
|
646
|
-
* Authentication type used for this test session
|
|
647
|
-
*/
|
|
648
|
-
authType: AuthType;
|
|
649
|
-
/**
|
|
650
|
-
* Playwright project name for this test session
|
|
651
|
-
*/
|
|
652
|
-
project?: string;
|
|
653
|
-
/**
|
|
654
|
-
* Lists all available tools from the MCP server
|
|
655
|
-
*
|
|
656
|
-
* @returns Array of tool definitions
|
|
657
|
-
*/
|
|
658
|
-
listTools(): Promise<Array<Tool>>;
|
|
659
|
-
/**
|
|
660
|
-
* Calls a tool on the MCP server
|
|
661
|
-
*
|
|
662
|
-
* @param name - Tool name
|
|
663
|
-
* @param args - Tool arguments
|
|
664
|
-
* @returns Tool call result
|
|
665
|
-
*/
|
|
666
|
-
callTool<TArgs extends Record<string, unknown> = Record<string, unknown>>(name: string, args: TArgs): Promise<CallToolResult>;
|
|
667
|
-
/**
|
|
668
|
-
* Gets information about the connected server
|
|
669
|
-
*/
|
|
670
|
-
getServerInfo(): {
|
|
671
|
-
name?: string;
|
|
672
|
-
version?: string;
|
|
673
|
-
} | null;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
691
|
/**
|
|
677
692
|
* Internal fixture state for passing auth type between fixtures
|
|
678
693
|
*/
|
package/dist/fixtures/mcp.js
CHANGED
|
@@ -101,6 +101,9 @@ function extractText(response) {
|
|
|
101
101
|
if (typeof r.text === "string") {
|
|
102
102
|
return r.text;
|
|
103
103
|
}
|
|
104
|
+
if (typeof r.response === "string") {
|
|
105
|
+
return r.response;
|
|
106
|
+
}
|
|
104
107
|
return JSON.stringify(r);
|
|
105
108
|
}
|
|
106
109
|
if (typeof response === "number" || typeof response === "boolean" || typeof response === "bigint") {
|
|
@@ -1176,21 +1179,109 @@ function createJudge(config = {}) {
|
|
|
1176
1179
|
);
|
|
1177
1180
|
}
|
|
1178
1181
|
}
|
|
1182
|
+
var REGISTRY_STATE_KEY = /* @__PURE__ */ Symbol.for(
|
|
1183
|
+
"mcp-server-tester.framework-registry-state"
|
|
1184
|
+
);
|
|
1185
|
+
var globalRegistry = globalThis;
|
|
1186
|
+
var existingRegistryState = globalRegistry[REGISTRY_STATE_KEY];
|
|
1187
|
+
var registryState = existingRegistryState ?? {
|
|
1188
|
+
datasets: /* @__PURE__ */ new Map(),
|
|
1189
|
+
hosts: /* @__PURE__ */ new Map(),
|
|
1190
|
+
judges: /* @__PURE__ */ new Map(),
|
|
1191
|
+
metrics: /* @__PURE__ */ new Map(),
|
|
1192
|
+
resultStores: /* @__PURE__ */ new Map()
|
|
1193
|
+
};
|
|
1194
|
+
if (!existingRegistryState) globalRegistry[REGISTRY_STATE_KEY] = registryState;
|
|
1195
|
+
function createRegistry(kind, implementations) {
|
|
1196
|
+
const typedImplementations = implementations;
|
|
1197
|
+
return {
|
|
1198
|
+
register(implementation) {
|
|
1199
|
+
const existing = typedImplementations.get(implementation.name);
|
|
1200
|
+
if (existing && existing !== implementation) {
|
|
1201
|
+
throw new Error(
|
|
1202
|
+
`${kind} "${implementation.name}" is already registered.`
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
typedImplementations.set(implementation.name, implementation);
|
|
1206
|
+
},
|
|
1207
|
+
get(name) {
|
|
1208
|
+
const implementation = typedImplementations.get(name);
|
|
1209
|
+
if (!implementation) {
|
|
1210
|
+
const available = [...typedImplementations.keys()].sort().join(", ");
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`${kind} "${name}" is not registered.${available ? ` Available: ${available}.` : ""}`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
return implementation;
|
|
1216
|
+
},
|
|
1217
|
+
list() {
|
|
1218
|
+
return [...typedImplementations.values()].sort(
|
|
1219
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
1220
|
+
);
|
|
1221
|
+
},
|
|
1222
|
+
clear() {
|
|
1223
|
+
typedImplementations.clear();
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
createRegistry(
|
|
1228
|
+
"Dataset source",
|
|
1229
|
+
registryState.datasets
|
|
1230
|
+
);
|
|
1231
|
+
createRegistry("Host", registryState.hosts);
|
|
1232
|
+
var judges = createRegistry("Judge", registryState.judges);
|
|
1233
|
+
createRegistry(
|
|
1234
|
+
"Metric",
|
|
1235
|
+
registryState.metrics
|
|
1236
|
+
);
|
|
1237
|
+
createRegistry(
|
|
1238
|
+
"Result store",
|
|
1239
|
+
registryState.resultStores
|
|
1240
|
+
);
|
|
1241
|
+
var getJudge = (name) => judges.get(name);
|
|
1242
|
+
z.object({
|
|
1243
|
+
iterations: z.number().int().positive().optional(),
|
|
1244
|
+
maxCases: z.number().int().positive().optional(),
|
|
1245
|
+
concurrency: z.number().int().positive().optional(),
|
|
1246
|
+
filterTags: z.array(z.string().min(1)).optional()
|
|
1247
|
+
}).strict();
|
|
1179
1248
|
|
|
1180
1249
|
// src/judge/judgeRegistry.ts
|
|
1181
|
-
var registry = /* @__PURE__ */ new Map();
|
|
1182
1250
|
function getRegisteredJudge(name) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1251
|
+
try {
|
|
1252
|
+
const judge = getJudge(name);
|
|
1253
|
+
return judge.evaluate;
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
if (error instanceof Error && !error.message.includes("Available:")) {
|
|
1256
|
+
throw new Error(`${error.message} No judges are registered.`);
|
|
1257
|
+
}
|
|
1258
|
+
throw error;
|
|
1189
1259
|
}
|
|
1190
|
-
|
|
1260
|
+
}
|
|
1261
|
+
function getRegisteredJudgeOptions(name, options) {
|
|
1262
|
+
const parsed = getJudge(name).schema.parse(options);
|
|
1263
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1264
|
+
throw new Error(`Judge "${name}" schema must return an options object.`);
|
|
1265
|
+
}
|
|
1266
|
+
return parsed;
|
|
1191
1267
|
}
|
|
1192
1268
|
|
|
1193
1269
|
// src/assertions/validators/judge.ts
|
|
1270
|
+
var judgeFrameworkOptionKeys = /* @__PURE__ */ new Set([
|
|
1271
|
+
"options",
|
|
1272
|
+
"judge",
|
|
1273
|
+
"rubric",
|
|
1274
|
+
"reference",
|
|
1275
|
+
"threshold",
|
|
1276
|
+
"reps",
|
|
1277
|
+
"provider",
|
|
1278
|
+
"model",
|
|
1279
|
+
"apiKeyEnvVar",
|
|
1280
|
+
"maxTokens",
|
|
1281
|
+
"temperature",
|
|
1282
|
+
"maxBudgetUsd",
|
|
1283
|
+
"maxToolOutputSize"
|
|
1284
|
+
]);
|
|
1194
1285
|
function computeStdDev(scores, mean) {
|
|
1195
1286
|
if (scores.length <= 1) return 0;
|
|
1196
1287
|
const variance = scores.reduce((sum, s) => sum + (s - mean) ** 2, 0) / scores.length;
|
|
@@ -1214,12 +1305,25 @@ async function validateJudge(response, config) {
|
|
|
1214
1305
|
if (judgeName !== void 0) {
|
|
1215
1306
|
try {
|
|
1216
1307
|
const executor = getRegisteredJudge(judgeName);
|
|
1217
|
-
const
|
|
1308
|
+
const options = getRegisteredJudgeOptions(
|
|
1309
|
+
judgeName,
|
|
1310
|
+
config.options ?? Object.fromEntries(
|
|
1311
|
+
Object.entries(config).filter(
|
|
1312
|
+
([key]) => !judgeFrameworkOptionKeys.has(key)
|
|
1313
|
+
)
|
|
1314
|
+
)
|
|
1315
|
+
);
|
|
1316
|
+
const judgeResult = await executor(
|
|
1317
|
+
response,
|
|
1318
|
+
reference ?? void 0,
|
|
1319
|
+
options
|
|
1320
|
+
);
|
|
1218
1321
|
const score = judgeResult.score;
|
|
1219
1322
|
const passed = score >= threshold;
|
|
1220
1323
|
return {
|
|
1221
1324
|
pass: passed,
|
|
1222
|
-
message: passed ? `Custom judge "${judgeName}" passed with score ${score.toFixed(2)}` : `Custom judge "${judgeName}" failed with score ${score.toFixed(2)} (threshold: ${threshold}). ${judgeResult.reasoning ?? ""}
|
|
1325
|
+
message: passed ? `Custom judge "${judgeName}" passed with score ${score.toFixed(2)}` : `Custom judge "${judgeName}" failed with score ${score.toFixed(2)} (threshold: ${threshold}). ${judgeResult.reasoning ?? ""}`,
|
|
1326
|
+
details: { score, reasoning: judgeResult.reasoning }
|
|
1223
1327
|
};
|
|
1224
1328
|
} catch (err) {
|
|
1225
1329
|
return {
|
|
@@ -1309,7 +1413,8 @@ async function runSingleJudge(received, rubric, options) {
|
|
|
1309
1413
|
reps,
|
|
1310
1414
|
provider,
|
|
1311
1415
|
model,
|
|
1312
|
-
judge
|
|
1416
|
+
judge,
|
|
1417
|
+
options: judgeOptions
|
|
1313
1418
|
} = options;
|
|
1314
1419
|
const validation = await validateJudge(received, {
|
|
1315
1420
|
...rubric !== void 0 && { rubric },
|
|
@@ -1318,7 +1423,8 @@ async function runSingleJudge(received, rubric, options) {
|
|
|
1318
1423
|
...reps !== void 0 && { reps },
|
|
1319
1424
|
...provider !== void 0 && { provider },
|
|
1320
1425
|
...model !== void 0 && { model },
|
|
1321
|
-
...judge !== void 0 && { judge }
|
|
1426
|
+
...judge !== void 0 && { judge },
|
|
1427
|
+
...judgeOptions !== void 0 && { options: judgeOptions }
|
|
1322
1428
|
});
|
|
1323
1429
|
return { pass: validation.pass, message: validation.message };
|
|
1324
1430
|
}
|
|
@@ -1491,10 +1597,22 @@ function partialMatch(actual, expected) {
|
|
|
1491
1597
|
return JSON.stringify(actualVal) === JSON.stringify(v);
|
|
1492
1598
|
});
|
|
1493
1599
|
}
|
|
1600
|
+
function matchesIdentity(call, expected) {
|
|
1601
|
+
return (call.name === expected.name || call.server !== void 0 && `${call.server}.${call.name}` === expected.name) && (expected.kind === void 0 || (call.kind ?? "tool_call") === expected.kind) && (expected.source === void 0 || call.source === expected.source) && (expected.server === void 0 || call.server === expected.server);
|
|
1602
|
+
}
|
|
1603
|
+
function unverifiedEvidence(response) {
|
|
1604
|
+
if (response.evidence === void 0 || response.evidence === "structured")
|
|
1605
|
+
return void 0;
|
|
1606
|
+
return {
|
|
1607
|
+
pass: false,
|
|
1608
|
+
message: `Host evidence is ${response.evidence}; structured tool evidence is required.`,
|
|
1609
|
+
details: { evidence: response.evidence }
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1494
1612
|
function findMatchingCall(actual, expected, startIndex = 0) {
|
|
1495
1613
|
for (let i = startIndex; i < actual.length; i++) {
|
|
1496
1614
|
const call = actual[i];
|
|
1497
|
-
if (call
|
|
1615
|
+
if (!matchesIdentity(call, expected)) continue;
|
|
1498
1616
|
if (expected.arguments !== void 0 && !partialMatch(call.arguments ?? {}, expected.arguments)) {
|
|
1499
1617
|
continue;
|
|
1500
1618
|
}
|
|
@@ -1506,18 +1624,22 @@ function validateToolCalls(response, expectation) {
|
|
|
1506
1624
|
if (!isSimulationResult(response)) {
|
|
1507
1625
|
return {
|
|
1508
1626
|
pass: false,
|
|
1509
|
-
message: "toolsTriggered expectation requires
|
|
1627
|
+
message: "toolsTriggered expectation requires a host simulation response with structured tool calls"
|
|
1510
1628
|
};
|
|
1511
1629
|
}
|
|
1512
|
-
const
|
|
1630
|
+
const unverified = unverifiedEvidence(response);
|
|
1631
|
+
if (unverified) return unverified;
|
|
1632
|
+
const actual = response.events ?? response.toolCalls;
|
|
1513
1633
|
const requiredCalls = expectation.calls.filter((c) => c.required !== false);
|
|
1514
1634
|
const calledRequiredCount = requiredCalls.filter(
|
|
1515
1635
|
(expected) => findMatchingCall(actual, expected) !== -1
|
|
1516
1636
|
).length;
|
|
1517
1637
|
const recall = requiredCalls.length > 0 ? calledRequiredCount / requiredCalls.length : 1;
|
|
1518
1638
|
const allowedNames = new Set(expectation.calls.map((c) => c.name));
|
|
1519
|
-
const precision = actual.length > 0 ? actual.filter(
|
|
1520
|
-
|
|
1639
|
+
const precision = actual.length > 0 ? actual.filter(
|
|
1640
|
+
(call) => expectation.calls.some((expected) => matchesIdentity(call, expected))
|
|
1641
|
+
).length / actual.length : 1;
|
|
1642
|
+
const metrics2 = { precision, recall };
|
|
1521
1643
|
const order = expectation.order ?? "any";
|
|
1522
1644
|
if (order === "strict") {
|
|
1523
1645
|
let searchFrom = 0;
|
|
@@ -1532,7 +1654,7 @@ function validateToolCalls(response, expectation) {
|
|
|
1532
1654
|
actual: actual.map((c) => c.name),
|
|
1533
1655
|
expected: expected.name
|
|
1534
1656
|
},
|
|
1535
|
-
metrics
|
|
1657
|
+
metrics: metrics2
|
|
1536
1658
|
};
|
|
1537
1659
|
}
|
|
1538
1660
|
} else {
|
|
@@ -1552,13 +1674,15 @@ function validateToolCalls(response, expectation) {
|
|
|
1552
1674
|
actual: actual.map((c) => c.name),
|
|
1553
1675
|
expected: expected.name
|
|
1554
1676
|
},
|
|
1555
|
-
metrics
|
|
1677
|
+
metrics: metrics2
|
|
1556
1678
|
};
|
|
1557
1679
|
}
|
|
1558
1680
|
}
|
|
1559
1681
|
}
|
|
1560
1682
|
if (expectation.exclusive === true) {
|
|
1561
|
-
const unexpected = actual.filter(
|
|
1683
|
+
const unexpected = actual.filter(
|
|
1684
|
+
(call) => !expectation.calls.some((expected) => matchesIdentity(call, expected))
|
|
1685
|
+
);
|
|
1562
1686
|
if (unexpected.length > 0) {
|
|
1563
1687
|
const names = unexpected.map((c) => `'${c.name}'`).join(", ");
|
|
1564
1688
|
return {
|
|
@@ -1568,20 +1692,24 @@ function validateToolCalls(response, expectation) {
|
|
|
1568
1692
|
actual: actual.map((c) => c.name),
|
|
1569
1693
|
unexpected: unexpected.map((c) => c.name)
|
|
1570
1694
|
},
|
|
1571
|
-
metrics
|
|
1695
|
+
metrics: metrics2
|
|
1572
1696
|
};
|
|
1573
1697
|
}
|
|
1574
1698
|
}
|
|
1575
|
-
return { pass: true, message: "All tool call expectations met", metrics };
|
|
1699
|
+
return { pass: true, message: "All tool call expectations met", metrics: metrics2 };
|
|
1576
1700
|
}
|
|
1577
1701
|
function validateToolCallCount(response, options) {
|
|
1578
1702
|
if (!isSimulationResult(response)) {
|
|
1579
1703
|
return {
|
|
1580
1704
|
pass: false,
|
|
1581
|
-
message: "toolCallCount expectation requires
|
|
1705
|
+
message: "toolCallCount expectation requires a host simulation response with structured tool calls"
|
|
1582
1706
|
};
|
|
1583
1707
|
}
|
|
1584
|
-
const
|
|
1708
|
+
const unverified = unverifiedEvidence(response);
|
|
1709
|
+
if (unverified) return unverified;
|
|
1710
|
+
const count = (response.events ?? response.toolCalls).filter(
|
|
1711
|
+
(call) => (call.kind ?? "tool_call") === "tool_call"
|
|
1712
|
+
).length;
|
|
1585
1713
|
const { min, max, exact } = options;
|
|
1586
1714
|
if (exact !== void 0 && count !== exact) {
|
|
1587
1715
|
return {
|
|
@@ -1665,14 +1793,19 @@ var MCPClientCredentialsConfigSchema = z.object({
|
|
|
1665
1793
|
});
|
|
1666
1794
|
var MCPAuthConfigSchema = z.object({
|
|
1667
1795
|
accessToken: z.string().optional(),
|
|
1796
|
+
accessTokenEnv: z.string().min(1).optional(),
|
|
1668
1797
|
oauth: MCPOAuthConfigSchema.optional(),
|
|
1669
1798
|
clientCredentials: MCPClientCredentialsConfigSchema.optional()
|
|
1670
1799
|
}).refine(
|
|
1800
|
+
(data) => !(data.accessToken && data.accessTokenEnv),
|
|
1801
|
+
"Cannot specify both accessToken and accessTokenEnv"
|
|
1802
|
+
).refine(
|
|
1671
1803
|
(data) => !(data.accessToken && data.oauth),
|
|
1672
1804
|
"Cannot specify both accessToken and oauth configuration"
|
|
1673
1805
|
);
|
|
1674
1806
|
var StdioConfigSchema = z.object({
|
|
1675
1807
|
transport: z.literal("stdio"),
|
|
1808
|
+
label: z.string().min(1).optional(),
|
|
1676
1809
|
command: z.string().min(1, "command is required for stdio transport"),
|
|
1677
1810
|
args: z.array(z.string()).optional(),
|
|
1678
1811
|
cwd: z.string().optional(),
|
|
@@ -1688,6 +1821,7 @@ function isLocalhost(hostname) {
|
|
|
1688
1821
|
}
|
|
1689
1822
|
var HttpConfigSchema = z.object({
|
|
1690
1823
|
transport: z.literal("http"),
|
|
1824
|
+
label: z.string().min(1).optional(),
|
|
1691
1825
|
serverUrl: z.string().url("serverUrl must be a valid URL").refine((url) => {
|
|
1692
1826
|
let parsed;
|
|
1693
1827
|
try {
|
|
@@ -1740,7 +1874,7 @@ var debugHttp = createDebug(`${NAMESPACE}:http`);
|
|
|
1740
1874
|
|
|
1741
1875
|
// package.json
|
|
1742
1876
|
var package_default = {
|
|
1743
|
-
version: "
|
|
1877
|
+
version: "2.0.0-beta.0"};
|
|
1744
1878
|
var debug = createDebug("mcp-server-tester:oauth-flow");
|
|
1745
1879
|
async function generatePKCE() {
|
|
1746
1880
|
const codeVerifier = oauth.generateRandomCodeVerifier();
|
|
@@ -2090,7 +2224,7 @@ async function createMCPClientForConfig(config, options) {
|
|
|
2090
2224
|
debugHttp("Attempting transport: streamableHttp");
|
|
2091
2225
|
const streamableTransport = new StreamableHTTPClientTransport(url, {
|
|
2092
2226
|
requestInit,
|
|
2093
|
-
authProvider: options
|
|
2227
|
+
...options?.authProvider ? { authProvider: options.authProvider } : {}
|
|
2094
2228
|
});
|
|
2095
2229
|
await client.connect(streamableTransport, connectOptions);
|
|
2096
2230
|
debugClient("Connected via Streamable HTTP");
|
|
@@ -2104,7 +2238,7 @@ async function createMCPClientForConfig(config, options) {
|
|
|
2104
2238
|
debugHttp("Attempting transport: sse");
|
|
2105
2239
|
const sseTransport = new SSEClientTransport(url, {
|
|
2106
2240
|
requestInit,
|
|
2107
|
-
authProvider: options
|
|
2241
|
+
...options?.authProvider ? { authProvider: options.authProvider } : {}
|
|
2108
2242
|
});
|
|
2109
2243
|
await client.connect(sseTransport, connectOptions);
|
|
2110
2244
|
debugClient("Connected via SSE");
|