@gleanwork/mcp-server-tester 1.0.1 → 1.1.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/dist/cli/index.js +1 -1
- package/dist/fixtures/mcp.js +1 -1
- package/dist/fixtures/mcp.js.map +1 -1
- package/dist/index-BlqLvjnP.d.cts +4322 -0
- package/dist/index-BlqLvjnP.d.ts +4322 -0
- package/dist/index.cjs +767 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +551 -4365
- package/dist/index.d.ts +551 -4365
- package/dist/index.js +757 -44
- package/dist/index.js.map +1 -1
- package/dist/reporters/mcpReporter.cjs +313 -2
- package/dist/reporters/mcpReporter.cjs.map +1 -1
- package/dist/reporters/mcpReporter.d.cts +427 -3
- package/dist/reporters/mcpReporter.d.ts +427 -3
- package/dist/reporters/mcpReporter.js +313 -2
- package/dist/reporters/mcpReporter.js.map +1 -1
- package/dist/reporters/ui-dist/app.js +16 -16
- package/dist/reporters/ui-dist/styles.css +1 -1
- package/dist/types/index.cjs +19 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +9 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +8 -1
package/dist/index.cjs
CHANGED
|
@@ -3126,6 +3126,20 @@ var init_dist3 = __esm({
|
|
|
3126
3126
|
};
|
|
3127
3127
|
}
|
|
3128
3128
|
});
|
|
3129
|
+
|
|
3130
|
+
// src/assertions/validators/types.ts
|
|
3131
|
+
var SnapshotSanitizers = {
|
|
3132
|
+
/** Replaces Unix timestamps (seconds and milliseconds) with a stable placeholder */
|
|
3133
|
+
TIMESTAMP: "timestamp",
|
|
3134
|
+
/** Replaces UUID v1-v5 strings with a stable placeholder */
|
|
3135
|
+
UUID: "uuid",
|
|
3136
|
+
/** Replaces ISO 8601 date/datetime strings with a stable placeholder */
|
|
3137
|
+
ISO_DATE: "iso-date",
|
|
3138
|
+
/** Replaces MongoDB ObjectId strings with a stable placeholder */
|
|
3139
|
+
OBJECT_ID: "objectId",
|
|
3140
|
+
/** Replaces JWT tokens with a stable placeholder */
|
|
3141
|
+
JWT: "jwt"
|
|
3142
|
+
};
|
|
3129
3143
|
var MCPHostCapabilitiesSchema = zod.z.object({
|
|
3130
3144
|
sampling: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
3131
3145
|
roots: zod.z.object({
|
|
@@ -4411,7 +4425,7 @@ function escapeHtml(text) {
|
|
|
4411
4425
|
|
|
4412
4426
|
// package.json
|
|
4413
4427
|
var package_default = {
|
|
4414
|
-
version: "1.
|
|
4428
|
+
version: "1.1.1"};
|
|
4415
4429
|
|
|
4416
4430
|
// src/mcp/clientFactory.ts
|
|
4417
4431
|
function getRetryAfterDelayMs(err) {
|
|
@@ -5935,20 +5949,6 @@ async function validateJudge(response, config) {
|
|
|
5935
5949
|
}
|
|
5936
5950
|
}
|
|
5937
5951
|
|
|
5938
|
-
// src/assertions/validators/types.ts
|
|
5939
|
-
var SnapshotSanitizers = {
|
|
5940
|
-
/** Replaces Unix timestamps (seconds and milliseconds) with a stable placeholder */
|
|
5941
|
-
TIMESTAMP: "timestamp",
|
|
5942
|
-
/** Replaces UUID v1-v5 strings with a stable placeholder */
|
|
5943
|
-
UUID: "uuid",
|
|
5944
|
-
/** Replaces ISO 8601 date/datetime strings with a stable placeholder */
|
|
5945
|
-
ISO_DATE: "iso-date",
|
|
5946
|
-
/** Replaces MongoDB ObjectId strings with a stable placeholder */
|
|
5947
|
-
OBJECT_ID: "objectId",
|
|
5948
|
-
/** Replaces JWT tokens with a stable placeholder */
|
|
5949
|
-
JWT: "jwt"
|
|
5950
|
-
};
|
|
5951
|
-
|
|
5952
5952
|
// src/mcp/fixtures/mcpFixture.ts
|
|
5953
5953
|
var DEFAULT_CALL_TIMEOUT_MS = 3e4;
|
|
5954
5954
|
function withCallTimeout(promise, ms, opName) {
|
|
@@ -6838,8 +6838,8 @@ Hint: run \`getMissingDependencyMessage('${provider}')\` or check docs/mcp-host.
|
|
|
6838
6838
|
Hint: check your API key environment variable (e.g. ANTHROPIC_API_KEY, GOOGLE_APPLICATION_CREDENTIALS).`;
|
|
6839
6839
|
}
|
|
6840
6840
|
if (raw.includes("404") || raw.includes("Not Found") || raw.toLowerCase().includes("model") && raw.toLowerCase().includes("not found")) {
|
|
6841
|
-
return `MCP host simulation failed:
|
|
6842
|
-
Hint:
|
|
6841
|
+
return `MCP host simulation failed: ${raw}
|
|
6842
|
+
Hint: a 404 usually means the model id is wrong or retired, or a base-URL override (e.g. ANTHROPIC_BASE_URL / OPENAI_BASE_URL pointing at a gateway) is routing requests somewhere that doesn't serve this model. Verify the model id and that no unexpected *_BASE_URL is set.`;
|
|
6843
6843
|
}
|
|
6844
6844
|
if (raw.includes("ENOTFOUND") || raw.includes("fetch failed") || raw.includes("ECONNREFUSED")) {
|
|
6845
6845
|
return `MCP host simulation failed: network error.
|
|
@@ -6937,12 +6937,18 @@ function createVercelOrchestrator() {
|
|
|
6937
6937
|
tools[toolName] = {
|
|
6938
6938
|
description: mcpTool.description ?? "",
|
|
6939
6939
|
inputSchema: jsonSchema2(rawSchema),
|
|
6940
|
-
execute: async (args) => {
|
|
6940
|
+
execute: async (args, opts) => {
|
|
6941
6941
|
const mcpStart = Date.now();
|
|
6942
6942
|
const result2 = await mcp.callTool(toolName, args);
|
|
6943
6943
|
mcpDurationMs += Date.now() - mcpStart;
|
|
6944
|
-
|
|
6945
|
-
|
|
6944
|
+
const output = extractText(result2);
|
|
6945
|
+
allToolCalls.push({
|
|
6946
|
+
id: opts?.toolCallId,
|
|
6947
|
+
name: toolName,
|
|
6948
|
+
arguments: args,
|
|
6949
|
+
output
|
|
6950
|
+
});
|
|
6951
|
+
return output;
|
|
6946
6952
|
}
|
|
6947
6953
|
};
|
|
6948
6954
|
}
|
|
@@ -6964,10 +6970,17 @@ function createVercelOrchestrator() {
|
|
|
6964
6970
|
totalCostUsd: 0,
|
|
6965
6971
|
durationMs: llmDurationMs
|
|
6966
6972
|
} : void 0;
|
|
6967
|
-
const conversationHistory = (result.steps ?? []).
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6973
|
+
const conversationHistory = (result.steps ?? []).flatMap((step) => {
|
|
6974
|
+
if (step.toolCalls?.length > 0) {
|
|
6975
|
+
return step.toolCalls.map(
|
|
6976
|
+
(tc) => ({
|
|
6977
|
+
role: "tool",
|
|
6978
|
+
toolCallId: tc.toolCallId
|
|
6979
|
+
})
|
|
6980
|
+
);
|
|
6981
|
+
}
|
|
6982
|
+
return step.text ? [{ role: "assistant", content: step.text }] : [];
|
|
6983
|
+
});
|
|
6971
6984
|
return {
|
|
6972
6985
|
success: true,
|
|
6973
6986
|
toolCalls: allToolCalls,
|
|
@@ -6994,6 +7007,7 @@ function parseStreamJson(stdout) {
|
|
|
6994
7007
|
const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
|
|
6995
7008
|
const toolCalls = [];
|
|
6996
7009
|
const textParts = [];
|
|
7010
|
+
let usage;
|
|
6997
7011
|
const conversationHistory = [];
|
|
6998
7012
|
for (const line of lines) {
|
|
6999
7013
|
let event;
|
|
@@ -7022,20 +7036,38 @@ function parseStreamJson(stdout) {
|
|
|
7022
7036
|
for (const block of event.message.content) {
|
|
7023
7037
|
if (block.type === "tool_result") {
|
|
7024
7038
|
const content = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
|
|
7025
|
-
|
|
7039
|
+
const call = block.tool_use_id ? toolCalls.find((tc) => tc.id === block.tool_use_id) : void 0;
|
|
7040
|
+
if (call) {
|
|
7041
|
+
call.output = content;
|
|
7042
|
+
conversationHistory.push({ role: "tool", toolCallId: call.id });
|
|
7043
|
+
} else {
|
|
7044
|
+
conversationHistory.push({ role: "tool", content });
|
|
7045
|
+
}
|
|
7026
7046
|
}
|
|
7027
7047
|
}
|
|
7028
7048
|
}
|
|
7029
|
-
if (event.type === "result"
|
|
7030
|
-
if (textParts.length === 0) {
|
|
7049
|
+
if (event.type === "result") {
|
|
7050
|
+
if (typeof event.result === "string" && textParts.length === 0) {
|
|
7031
7051
|
textParts.push(event.result);
|
|
7032
7052
|
}
|
|
7053
|
+
if (event.usage) {
|
|
7054
|
+
usage = {
|
|
7055
|
+
inputTokens: event.usage.input_tokens ?? 0,
|
|
7056
|
+
outputTokens: event.usage.output_tokens ?? 0,
|
|
7057
|
+
totalCostUsd: event.total_cost_usd ?? 0,
|
|
7058
|
+
durationMs: event.duration_ms ?? 0,
|
|
7059
|
+
durationApiMs: event.duration_api_ms,
|
|
7060
|
+
cacheReadInputTokens: event.usage.cache_read_input_tokens,
|
|
7061
|
+
cacheCreationInputTokens: event.usage.cache_creation_input_tokens
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7033
7064
|
}
|
|
7034
7065
|
if (event.type === "result" && event.is_error === true) {
|
|
7035
7066
|
return {
|
|
7036
7067
|
success: false,
|
|
7037
7068
|
toolCalls,
|
|
7038
|
-
error: typeof event.result === "string" ? event.result : "CLI host reported an error"
|
|
7069
|
+
error: typeof event.result === "string" ? event.result : "CLI host reported an error",
|
|
7070
|
+
usage
|
|
7039
7071
|
};
|
|
7040
7072
|
}
|
|
7041
7073
|
}
|
|
@@ -7047,7 +7079,8 @@ function parseStreamJson(stdout) {
|
|
|
7047
7079
|
success: true,
|
|
7048
7080
|
toolCalls,
|
|
7049
7081
|
response: response || void 0,
|
|
7050
|
-
conversationHistory: conversationHistory.length > 0 ? conversationHistory : void 0
|
|
7082
|
+
conversationHistory: conversationHistory.length > 0 ? conversationHistory : void 0,
|
|
7083
|
+
usage
|
|
7051
7084
|
};
|
|
7052
7085
|
}
|
|
7053
7086
|
function createJsonParser(paths) {
|
|
@@ -7293,6 +7326,216 @@ function buildBaselinePassMap(baseline) {
|
|
|
7293
7326
|
}
|
|
7294
7327
|
return map;
|
|
7295
7328
|
}
|
|
7329
|
+
var KIND_DIRS = {
|
|
7330
|
+
"eval-runner-result": "eval-runs",
|
|
7331
|
+
"reporter-run": "reporter-runs",
|
|
7332
|
+
"eval-run-comparison": "comparisons/eval-runs",
|
|
7333
|
+
"server-comparison": "comparisons/servers"
|
|
7334
|
+
};
|
|
7335
|
+
function createEvalResultStore(config) {
|
|
7336
|
+
if (config.provider === "file") {
|
|
7337
|
+
return new FileEvalResultStore(config);
|
|
7338
|
+
}
|
|
7339
|
+
return new GCSEvalResultStore(config);
|
|
7340
|
+
}
|
|
7341
|
+
function resolveEvalResultStore(store) {
|
|
7342
|
+
return isEvalResultStore(store) ? store : createEvalResultStore(store);
|
|
7343
|
+
}
|
|
7344
|
+
function isEvalResultStore(value) {
|
|
7345
|
+
return typeof value === "object" && value !== null && "saveArtifact" in value && "loadArtifact" in value && "loadLatestArtifact" in value && "listArtifacts" in value;
|
|
7346
|
+
}
|
|
7347
|
+
function createStoredEvalArtifact(options) {
|
|
7348
|
+
const createdAt = options.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
7349
|
+
return {
|
|
7350
|
+
schemaVersion: 1,
|
|
7351
|
+
kind: options.kind,
|
|
7352
|
+
id: options.id ?? createDefaultArtifactId(createdAt),
|
|
7353
|
+
createdAt,
|
|
7354
|
+
metadata: {
|
|
7355
|
+
...defaultEnvironmentMetadata(),
|
|
7356
|
+
...options.metadata ?? {}
|
|
7357
|
+
},
|
|
7358
|
+
data: options.data
|
|
7359
|
+
};
|
|
7360
|
+
}
|
|
7361
|
+
function createDefaultArtifactId(timestamp = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
7362
|
+
const safeTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
7363
|
+
const runNumber = process.env.GITHUB_RUN_NUMBER;
|
|
7364
|
+
const sha = process.env.GITHUB_SHA?.slice(0, 12);
|
|
7365
|
+
const suffix = runNumber ?? sha;
|
|
7366
|
+
return suffix ? `${safeTimestamp}-${suffix}` : safeTimestamp;
|
|
7367
|
+
}
|
|
7368
|
+
function defaultEnvironmentMetadata() {
|
|
7369
|
+
return {
|
|
7370
|
+
...process.env.GITHUB_SHA !== void 0 && {
|
|
7371
|
+
gitHash: process.env.GITHUB_SHA
|
|
7372
|
+
},
|
|
7373
|
+
...process.env.GITHUB_REF_NAME !== void 0 && {
|
|
7374
|
+
branch: process.env.GITHUB_REF_NAME
|
|
7375
|
+
},
|
|
7376
|
+
...process.env.GITHUB_RUN_NUMBER !== void 0 && {
|
|
7377
|
+
runNumber: process.env.GITHUB_RUN_NUMBER
|
|
7378
|
+
},
|
|
7379
|
+
...process.env.GITHUB_EVENT_NAME !== void 0 && {
|
|
7380
|
+
trigger: process.env.GITHUB_EVENT_NAME
|
|
7381
|
+
}
|
|
7382
|
+
};
|
|
7383
|
+
}
|
|
7384
|
+
var FileEvalResultStore = class {
|
|
7385
|
+
dir;
|
|
7386
|
+
constructor(config) {
|
|
7387
|
+
this.dir = config.dir;
|
|
7388
|
+
}
|
|
7389
|
+
async saveArtifact(artifact) {
|
|
7390
|
+
const artifactDir = path2.join(this.dir, KIND_DIRS[artifact.kind]);
|
|
7391
|
+
await fs$1.mkdir(artifactDir, { recursive: true });
|
|
7392
|
+
const serialized = JSON.stringify(artifact, null, 2);
|
|
7393
|
+
await fs$1.writeFile(
|
|
7394
|
+
path2.join(artifactDir, `${artifact.id}.json`),
|
|
7395
|
+
serialized,
|
|
7396
|
+
"utf8"
|
|
7397
|
+
);
|
|
7398
|
+
await fs$1.writeFile(path2.join(artifactDir, "latest.json"), serialized, "utf8");
|
|
7399
|
+
}
|
|
7400
|
+
async loadArtifact(kind, id) {
|
|
7401
|
+
const raw = await fs$1.readFile(
|
|
7402
|
+
path2.join(this.dir, KIND_DIRS[kind], `${id}.json`),
|
|
7403
|
+
"utf8"
|
|
7404
|
+
);
|
|
7405
|
+
return JSON.parse(raw);
|
|
7406
|
+
}
|
|
7407
|
+
async loadLatestArtifact(kind) {
|
|
7408
|
+
try {
|
|
7409
|
+
const raw = await fs$1.readFile(
|
|
7410
|
+
path2.join(this.dir, KIND_DIRS[kind], "latest.json"),
|
|
7411
|
+
"utf8"
|
|
7412
|
+
);
|
|
7413
|
+
return JSON.parse(raw);
|
|
7414
|
+
} catch (error) {
|
|
7415
|
+
if (isMissingFileError(error)) {
|
|
7416
|
+
return null;
|
|
7417
|
+
}
|
|
7418
|
+
throw error;
|
|
7419
|
+
}
|
|
7420
|
+
}
|
|
7421
|
+
async listArtifacts(kind, options = {}) {
|
|
7422
|
+
let files;
|
|
7423
|
+
try {
|
|
7424
|
+
files = await fs$1.readdir(path2.join(this.dir, KIND_DIRS[kind]));
|
|
7425
|
+
} catch (error) {
|
|
7426
|
+
if (isMissingFileError(error)) {
|
|
7427
|
+
return [];
|
|
7428
|
+
}
|
|
7429
|
+
throw error;
|
|
7430
|
+
}
|
|
7431
|
+
const summaries = await Promise.all(
|
|
7432
|
+
files.filter((f) => f.endsWith(".json") && f !== "latest.json").map(async (file) => {
|
|
7433
|
+
const raw = await fs$1.readFile(
|
|
7434
|
+
path2.join(this.dir, KIND_DIRS[kind], file),
|
|
7435
|
+
"utf8"
|
|
7436
|
+
);
|
|
7437
|
+
return toSummary(JSON.parse(raw));
|
|
7438
|
+
})
|
|
7439
|
+
);
|
|
7440
|
+
return summaries.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, options.limit);
|
|
7441
|
+
}
|
|
7442
|
+
};
|
|
7443
|
+
var GCSEvalResultStore = class {
|
|
7444
|
+
bucketName;
|
|
7445
|
+
prefix;
|
|
7446
|
+
storage;
|
|
7447
|
+
constructor(config) {
|
|
7448
|
+
this.bucketName = config.bucket;
|
|
7449
|
+
this.prefix = trimSlashes(config.prefix ?? "");
|
|
7450
|
+
}
|
|
7451
|
+
async saveArtifact(artifact) {
|
|
7452
|
+
const bucket = await this.getBucket();
|
|
7453
|
+
const serialized = JSON.stringify(artifact, null, 2);
|
|
7454
|
+
await bucket.file(
|
|
7455
|
+
this.objectPath(
|
|
7456
|
+
artifact.kind,
|
|
7457
|
+
`${encodeURIComponent(artifact.id)}.json`
|
|
7458
|
+
)
|
|
7459
|
+
).save(serialized, {
|
|
7460
|
+
contentType: "application/json",
|
|
7461
|
+
resumable: false,
|
|
7462
|
+
validation: false
|
|
7463
|
+
});
|
|
7464
|
+
await bucket.file(this.objectPath(artifact.kind, "latest.json")).save(serialized, {
|
|
7465
|
+
contentType: "application/json",
|
|
7466
|
+
resumable: false,
|
|
7467
|
+
validation: false
|
|
7468
|
+
});
|
|
7469
|
+
}
|
|
7470
|
+
async loadArtifact(kind, id) {
|
|
7471
|
+
const bucket = await this.getBucket();
|
|
7472
|
+
const file = bucket.file(
|
|
7473
|
+
this.objectPath(kind, `${encodeURIComponent(id)}.json`)
|
|
7474
|
+
);
|
|
7475
|
+
const [buffer] = await file.download();
|
|
7476
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
7477
|
+
}
|
|
7478
|
+
async loadLatestArtifact(kind) {
|
|
7479
|
+
const bucket = await this.getBucket();
|
|
7480
|
+
const file = bucket.file(this.objectPath(kind, "latest.json"));
|
|
7481
|
+
const [exists] = await file.exists();
|
|
7482
|
+
if (!exists) return null;
|
|
7483
|
+
const [buffer] = await file.download();
|
|
7484
|
+
return JSON.parse(buffer.toString("utf8"));
|
|
7485
|
+
}
|
|
7486
|
+
async listArtifacts(kind, options = {}) {
|
|
7487
|
+
const bucket = await this.getBucket();
|
|
7488
|
+
const [files] = await bucket.getFiles({
|
|
7489
|
+
prefix: this.objectPath(kind, "")
|
|
7490
|
+
});
|
|
7491
|
+
const summaries = await Promise.all(
|
|
7492
|
+
files.filter(
|
|
7493
|
+
(f) => f.name.endsWith(".json") && !f.name.endsWith("/latest.json")
|
|
7494
|
+
).map(async (file) => {
|
|
7495
|
+
const [buffer] = await file.download();
|
|
7496
|
+
return toSummary(
|
|
7497
|
+
JSON.parse(buffer.toString("utf8"))
|
|
7498
|
+
);
|
|
7499
|
+
})
|
|
7500
|
+
);
|
|
7501
|
+
return summaries.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, options.limit);
|
|
7502
|
+
}
|
|
7503
|
+
async getBucket() {
|
|
7504
|
+
if (!this.storage) {
|
|
7505
|
+
const moduleName = "@google-cloud/storage";
|
|
7506
|
+
let Storage;
|
|
7507
|
+
try {
|
|
7508
|
+
const mod = await import(moduleName);
|
|
7509
|
+
Storage = mod.Storage;
|
|
7510
|
+
} catch (error) {
|
|
7511
|
+
throw new Error(
|
|
7512
|
+
`GCS result storage requires the optional \`@google-cloud/storage\` package. Install it and authenticate with Application Default Credentials via GOOGLE_APPLICATION_CREDENTIALS.
|
|
7513
|
+
Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
7514
|
+
);
|
|
7515
|
+
}
|
|
7516
|
+
this.storage = new Storage();
|
|
7517
|
+
}
|
|
7518
|
+
return this.storage.bucket(this.bucketName);
|
|
7519
|
+
}
|
|
7520
|
+
objectPath(kind, filename) {
|
|
7521
|
+
const parts = [this.prefix, KIND_DIRS[kind], filename].filter(Boolean);
|
|
7522
|
+
return parts.join("/");
|
|
7523
|
+
}
|
|
7524
|
+
};
|
|
7525
|
+
function toSummary(artifact) {
|
|
7526
|
+
return {
|
|
7527
|
+
kind: artifact.kind,
|
|
7528
|
+
id: artifact.id,
|
|
7529
|
+
createdAt: artifact.createdAt,
|
|
7530
|
+
metadata: artifact.metadata
|
|
7531
|
+
};
|
|
7532
|
+
}
|
|
7533
|
+
function trimSlashes(value) {
|
|
7534
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
7535
|
+
}
|
|
7536
|
+
function isMissingFileError(error) {
|
|
7537
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
7538
|
+
}
|
|
7296
7539
|
var execFileAsync = util.promisify(child_process.execFile);
|
|
7297
7540
|
async function execFileNoThrow(file, args) {
|
|
7298
7541
|
try {
|
|
@@ -7339,6 +7582,41 @@ function sumUsage(a, b) {
|
|
|
7339
7582
|
}
|
|
7340
7583
|
|
|
7341
7584
|
// src/evals/evalRunner.ts
|
|
7585
|
+
function createToolOverrideMCP(mcp, variant) {
|
|
7586
|
+
return {
|
|
7587
|
+
...mcp,
|
|
7588
|
+
async listTools() {
|
|
7589
|
+
const tools = await mcp.listTools();
|
|
7590
|
+
const knownToolNames = new Set(tools.map((tool2) => tool2.name));
|
|
7591
|
+
const unknownToolNames = Object.keys(variant.tools).filter(
|
|
7592
|
+
(name15) => !knownToolNames.has(name15)
|
|
7593
|
+
);
|
|
7594
|
+
if (unknownToolNames.length > 0) {
|
|
7595
|
+
throw new Error(
|
|
7596
|
+
`[mcp-server-tester] toolOverrides variant "${variant.id}" references unknown tool(s): ` + unknownToolNames.join(", ")
|
|
7597
|
+
);
|
|
7598
|
+
}
|
|
7599
|
+
return tools.map((tool2) => {
|
|
7600
|
+
const override = variant.tools[tool2.name];
|
|
7601
|
+
if (!override) {
|
|
7602
|
+
return tool2;
|
|
7603
|
+
}
|
|
7604
|
+
return {
|
|
7605
|
+
...tool2,
|
|
7606
|
+
...override.description !== void 0 && {
|
|
7607
|
+
description: override.description
|
|
7608
|
+
},
|
|
7609
|
+
...override.inputSchema !== void 0 && {
|
|
7610
|
+
inputSchema: override.inputSchema
|
|
7611
|
+
}
|
|
7612
|
+
};
|
|
7613
|
+
});
|
|
7614
|
+
},
|
|
7615
|
+
async callTool(name15, args) {
|
|
7616
|
+
return mcp.callTool(name15, args);
|
|
7617
|
+
}
|
|
7618
|
+
};
|
|
7619
|
+
}
|
|
7342
7620
|
async function executeToolCall(evalCase, mcp) {
|
|
7343
7621
|
const mode = evalCase.mode || "direct";
|
|
7344
7622
|
try {
|
|
@@ -7523,9 +7801,12 @@ async function runExpectBlockValidations(expectBlock, response, config) {
|
|
|
7523
7801
|
}
|
|
7524
7802
|
return { expectations: results, toolPrecision, toolRecall };
|
|
7525
7803
|
}
|
|
7526
|
-
function buildRequest(evalCase) {
|
|
7804
|
+
function buildRequest(evalCase, toolOverrideVariantId) {
|
|
7527
7805
|
const request = {};
|
|
7528
7806
|
if (evalCase.description) request.description = evalCase.description;
|
|
7807
|
+
if (toolOverrideVariantId !== void 0) {
|
|
7808
|
+
request.toolOverrideVariantId = toolOverrideVariantId;
|
|
7809
|
+
}
|
|
7529
7810
|
if (evalCase.mode === "mcp_host") {
|
|
7530
7811
|
if (evalCase.scenario) request.scenario = evalCase.scenario;
|
|
7531
7812
|
if (evalCase.mcpHostConfig) {
|
|
@@ -7590,7 +7871,7 @@ async function runSingleIteration(evalCase, context, options) {
|
|
|
7590
7871
|
toolName: evalCase.scenario != null ? "mcp_host" : evalCase.toolName ?? "unknown",
|
|
7591
7872
|
source: "eval",
|
|
7592
7873
|
pass: didCasePass(error, expectationResults),
|
|
7593
|
-
request: buildRequest(evalCase),
|
|
7874
|
+
request: buildRequest(evalCase, options.toolOverrideVariantId),
|
|
7594
7875
|
response,
|
|
7595
7876
|
error,
|
|
7596
7877
|
expectations: expectationResults,
|
|
@@ -7669,7 +7950,8 @@ async function runEvalCase(evalCase, context, options = {}) {
|
|
|
7669
7950
|
authType: context.mcp.authType,
|
|
7670
7951
|
project: context.mcp.project,
|
|
7671
7952
|
durationMs: 0,
|
|
7672
|
-
tags: evalCase.tags
|
|
7953
|
+
tags: evalCase.tags,
|
|
7954
|
+
request: buildRequest(evalCase, options.toolOverrideVariantId)
|
|
7673
7955
|
};
|
|
7674
7956
|
const totalHostUsage = iterationResults.reduce(
|
|
7675
7957
|
(acc, r) => sumUsage(acc, r.hostUsage),
|
|
@@ -7716,6 +7998,7 @@ async function getGitHash() {
|
|
|
7716
7998
|
const result = await execFileNoThrow("git", ["rev-parse", "HEAD"]);
|
|
7717
7999
|
return result.status === 0 ? result.stdout.trim() : void 0;
|
|
7718
8000
|
}
|
|
8001
|
+
var warnedNoTestInfo = false;
|
|
7719
8002
|
async function runEvalDataset(options, context) {
|
|
7720
8003
|
const {
|
|
7721
8004
|
dataset,
|
|
@@ -7728,11 +8011,15 @@ async function runEvalDataset(options, context) {
|
|
|
7728
8011
|
filterTags,
|
|
7729
8012
|
saveResultsTo,
|
|
7730
8013
|
omitResponsesFromBaseline = true,
|
|
8014
|
+
redactStoredResponses,
|
|
8015
|
+
resultStore,
|
|
7731
8016
|
baselineResultsFrom,
|
|
8017
|
+
toolOverrides,
|
|
7732
8018
|
mcpHostModel,
|
|
7733
8019
|
judgeModel
|
|
7734
8020
|
} = options;
|
|
7735
8021
|
const startTime = Date.now();
|
|
8022
|
+
const effectiveContext = toolOverrides ? { ...context, mcp: createToolOverrideMCP(context.mcp, toolOverrides) } : context;
|
|
7736
8023
|
const allSchemas = {
|
|
7737
8024
|
...dataset.schemas,
|
|
7738
8025
|
...schemas
|
|
@@ -7764,9 +8051,10 @@ async function runEvalDataset(options, context) {
|
|
|
7764
8051
|
}
|
|
7765
8052
|
}
|
|
7766
8053
|
const effectiveCase = withIterations.judgeReps === void 0 && defaultJudgeReps !== void 0 ? { ...withIterations, judgeReps: defaultJudgeReps } : withIterations;
|
|
7767
|
-
const result2 = await runEvalCase(effectiveCase,
|
|
8054
|
+
const result2 = await runEvalCase(effectiveCase, effectiveContext, {
|
|
7768
8055
|
datasetName: dataset.name,
|
|
7769
|
-
schemas: allSchemas
|
|
8056
|
+
schemas: allSchemas,
|
|
8057
|
+
toolOverrideVariantId: toolOverrides?.id
|
|
7770
8058
|
});
|
|
7771
8059
|
if (onCaseComplete) {
|
|
7772
8060
|
await onCaseComplete(result2);
|
|
@@ -7791,6 +8079,9 @@ async function runEvalDataset(options, context) {
|
|
|
7791
8079
|
gitHash,
|
|
7792
8080
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7793
8081
|
packageVersion: package_default.version,
|
|
8082
|
+
...toolOverrides !== void 0 && {
|
|
8083
|
+
toolOverrideVariantId: toolOverrides.id
|
|
8084
|
+
},
|
|
7794
8085
|
...mcpHostModel !== void 0 && { mcpHostModel },
|
|
7795
8086
|
...judgeModel !== void 0 && { judgeModel }
|
|
7796
8087
|
};
|
|
@@ -7809,7 +8100,7 @@ async function runEvalDataset(options, context) {
|
|
|
7809
8100
|
};
|
|
7810
8101
|
if (baselineResultsFrom) {
|
|
7811
8102
|
try {
|
|
7812
|
-
const baseline = await loadBaseline(baselineResultsFrom);
|
|
8103
|
+
const baseline = typeof baselineResultsFrom === "string" ? await loadBaseline(baselineResultsFrom) : await loadStoredBaseline(baselineResultsFrom, resultStore);
|
|
7813
8104
|
const baselinePassRate = baseline.total > 0 ? baseline.passed / baseline.total : 0;
|
|
7814
8105
|
const baselineMap = buildBaselinePassMap(baseline);
|
|
7815
8106
|
const currentCaseIds = result.caseResults.map((cr) => cr.id);
|
|
@@ -7837,7 +8128,7 @@ async function runEvalDataset(options, context) {
|
|
|
7837
8128
|
result.deltaPassRate = result.total > 0 ? result.passed / result.total - baselinePassRate : 0;
|
|
7838
8129
|
} catch (err) {
|
|
7839
8130
|
console.warn(
|
|
7840
|
-
`[mcp-server-tester] Could not load baseline from ${baselineResultsFrom}: ${err instanceof Error ? err.message : String(err)}`
|
|
8131
|
+
`[mcp-server-tester] Could not load baseline from ${formatBaselineRef(baselineResultsFrom)}: ${err instanceof Error ? err.message : String(err)}`
|
|
7841
8132
|
);
|
|
7842
8133
|
}
|
|
7843
8134
|
}
|
|
@@ -7852,22 +8143,84 @@ async function runEvalDataset(options, context) {
|
|
|
7852
8143
|
result.datasetToolF1 = avgPrec + avgRecall > 0 ? 2 * avgPrec * avgRecall / (avgPrec + avgRecall) : 0;
|
|
7853
8144
|
}
|
|
7854
8145
|
if (saveResultsTo) {
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
8146
|
+
if (typeof saveResultsTo === "string") {
|
|
8147
|
+
await saveBaseline(result, saveResultsTo, {
|
|
8148
|
+
omitResponses: omitResponsesFromBaseline
|
|
8149
|
+
});
|
|
8150
|
+
} else {
|
|
8151
|
+
await saveStoredEvalResult(result, saveResultsTo, {
|
|
8152
|
+
resultStore,
|
|
8153
|
+
omitResponses: redactStoredResponses ?? true,
|
|
8154
|
+
metadata: {
|
|
8155
|
+
datasetName: dataset.name,
|
|
8156
|
+
...toolOverrides?.id !== void 0 && {
|
|
8157
|
+
toolOverrideVariantId: toolOverrides.id
|
|
8158
|
+
},
|
|
8159
|
+
...mcpHostModel !== void 0 && { mcpHostModel },
|
|
8160
|
+
...judgeModel !== void 0 && { judgeModel },
|
|
8161
|
+
...gitHash !== void 0 && { gitHash },
|
|
8162
|
+
packageVersion: package_default.version
|
|
8163
|
+
}
|
|
8164
|
+
});
|
|
8165
|
+
}
|
|
7858
8166
|
}
|
|
7859
8167
|
if (context.testInfo) {
|
|
7860
8168
|
await context.testInfo.attach("mcp-test-results", {
|
|
7861
8169
|
contentType: "application/json",
|
|
7862
8170
|
body: Buffer.from(JSON.stringify({ caseResults }))
|
|
7863
8171
|
});
|
|
7864
|
-
} else if (caseResults.length > 0) {
|
|
8172
|
+
} else if (caseResults.length > 0 && !warnedNoTestInfo) {
|
|
8173
|
+
warnedNoTestInfo = true;
|
|
7865
8174
|
console.warn(
|
|
7866
8175
|
"[mcp-server-tester] runEvalDataset: testInfo not provided \u2014 results will not appear in the MCP reporter.\nTo enable reporting, pass testInfo from the Playwright test function:\n await runEvalDataset({ dataset }, { mcp, testInfo });"
|
|
7867
8176
|
);
|
|
7868
8177
|
}
|
|
7869
8178
|
return result;
|
|
7870
8179
|
}
|
|
8180
|
+
async function loadStoredBaseline(baselineResultsFrom, resultStore) {
|
|
8181
|
+
if (!resultStore) {
|
|
8182
|
+
throw new Error("resultStore is required for store-backed baselines");
|
|
8183
|
+
}
|
|
8184
|
+
const store = resolveEvalResultStore(resultStore);
|
|
8185
|
+
const artifact = baselineResultsFrom.ref === "latest" ? await store.loadLatestArtifact("eval-runner-result") : await store.loadArtifact(
|
|
8186
|
+
"eval-runner-result",
|
|
8187
|
+
baselineResultsFrom.ref.id
|
|
8188
|
+
);
|
|
8189
|
+
if (!artifact) {
|
|
8190
|
+
throw new Error("No latest eval run artifact found");
|
|
8191
|
+
}
|
|
8192
|
+
return artifact.data;
|
|
8193
|
+
}
|
|
8194
|
+
async function saveStoredEvalResult(result, saveResultsTo, options) {
|
|
8195
|
+
if (!options.resultStore) {
|
|
8196
|
+
throw new Error("resultStore is required for store-backed saves");
|
|
8197
|
+
}
|
|
8198
|
+
const store = resolveEvalResultStore(options.resultStore);
|
|
8199
|
+
const data = options.omitResponses ? omitResponsesFromResult(result) : result;
|
|
8200
|
+
const id = saveResultsTo.ref && saveResultsTo.ref !== "latest" ? saveResultsTo.ref.id : void 0;
|
|
8201
|
+
await store.saveArtifact(
|
|
8202
|
+
createStoredEvalArtifact({
|
|
8203
|
+
kind: "eval-runner-result",
|
|
8204
|
+
id,
|
|
8205
|
+
data,
|
|
8206
|
+
metadata: options.metadata
|
|
8207
|
+
})
|
|
8208
|
+
);
|
|
8209
|
+
}
|
|
8210
|
+
function omitResponsesFromResult(result) {
|
|
8211
|
+
return {
|
|
8212
|
+
...result,
|
|
8213
|
+
caseResults: result.caseResults.map(
|
|
8214
|
+
({ response: _response, ...rest }) => rest
|
|
8215
|
+
)
|
|
8216
|
+
};
|
|
8217
|
+
}
|
|
8218
|
+
function formatBaselineRef(baselineResultsFrom) {
|
|
8219
|
+
if (typeof baselineResultsFrom === "string") {
|
|
8220
|
+
return baselineResultsFrom;
|
|
8221
|
+
}
|
|
8222
|
+
return baselineResultsFrom.ref === "latest" ? "resultStore latest" : `resultStore ${baselineResultsFrom.ref.id}`;
|
|
8223
|
+
}
|
|
7871
8224
|
|
|
7872
8225
|
// src/evals/serverComparison.ts
|
|
7873
8226
|
async function runServerComparison(options, contextA, contextB) {
|
|
@@ -7909,7 +8262,7 @@ async function runServerComparison(options, contextA, contextB) {
|
|
|
7909
8262
|
}
|
|
7910
8263
|
const total = cases.length;
|
|
7911
8264
|
const decidedCases = aWins + bWins + ties;
|
|
7912
|
-
|
|
8265
|
+
const comparison = {
|
|
7913
8266
|
dataset: options.dataset.name,
|
|
7914
8267
|
total,
|
|
7915
8268
|
aWins,
|
|
@@ -7926,6 +8279,366 @@ async function runServerComparison(options, contextA, contextB) {
|
|
|
7926
8279
|
serverBResult: resultB,
|
|
7927
8280
|
durationMs: Date.now() - startTime
|
|
7928
8281
|
};
|
|
8282
|
+
if (options.comparisonStore) {
|
|
8283
|
+
await saveServerComparison({
|
|
8284
|
+
store: options.comparisonStore,
|
|
8285
|
+
comparison,
|
|
8286
|
+
id: options.comparisonId,
|
|
8287
|
+
metadata: {
|
|
8288
|
+
datasetName: options.dataset.name,
|
|
8289
|
+
...options.comparisonMetadata ?? {}
|
|
8290
|
+
},
|
|
8291
|
+
redactStoredResponses: options.redactStoredResponses
|
|
8292
|
+
});
|
|
8293
|
+
}
|
|
8294
|
+
return comparison;
|
|
8295
|
+
}
|
|
8296
|
+
async function saveServerComparison(options) {
|
|
8297
|
+
const store = resolveEvalResultStore(options.store);
|
|
8298
|
+
const data = options.redactStoredResponses ? redactResponses(options.comparison) : options.comparison;
|
|
8299
|
+
const artifact = createStoredEvalArtifact({
|
|
8300
|
+
kind: "server-comparison",
|
|
8301
|
+
id: options.id,
|
|
8302
|
+
data,
|
|
8303
|
+
metadata: {
|
|
8304
|
+
datasetName: options.comparison.dataset,
|
|
8305
|
+
...options.metadata ?? {}
|
|
8306
|
+
}
|
|
8307
|
+
});
|
|
8308
|
+
await store.saveArtifact(artifact);
|
|
8309
|
+
return artifact;
|
|
8310
|
+
}
|
|
8311
|
+
function redactResponses(value) {
|
|
8312
|
+
return JSON.parse(
|
|
8313
|
+
JSON.stringify(
|
|
8314
|
+
value,
|
|
8315
|
+
(key, currentValue) => key === "response" ? void 0 : currentValue
|
|
8316
|
+
)
|
|
8317
|
+
);
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8320
|
+
// src/evals/evalRunComparison.ts
|
|
8321
|
+
function compareEvalRuns(options) {
|
|
8322
|
+
const { baseline, candidate, labels } = options;
|
|
8323
|
+
const candidateMap = new Map(
|
|
8324
|
+
candidate.caseResults.map((result) => [result.id, result])
|
|
8325
|
+
);
|
|
8326
|
+
const cases = [];
|
|
8327
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
8328
|
+
for (const baselineCase of baseline.caseResults) {
|
|
8329
|
+
seenIds.add(baselineCase.id);
|
|
8330
|
+
const candidateCase = candidateMap.get(baselineCase.id);
|
|
8331
|
+
if (!candidateCase) {
|
|
8332
|
+
cases.push({
|
|
8333
|
+
id: baselineCase.id,
|
|
8334
|
+
outcome: "MISSING_FROM_CANDIDATE",
|
|
8335
|
+
baseline: baselineCase
|
|
8336
|
+
});
|
|
8337
|
+
continue;
|
|
8338
|
+
}
|
|
8339
|
+
cases.push({
|
|
8340
|
+
id: baselineCase.id,
|
|
8341
|
+
outcome: compareCaseOutcome(baselineCase.pass, candidateCase.pass),
|
|
8342
|
+
baseline: baselineCase,
|
|
8343
|
+
candidate: candidateCase
|
|
8344
|
+
});
|
|
8345
|
+
}
|
|
8346
|
+
for (const candidateCase of candidate.caseResults) {
|
|
8347
|
+
if (seenIds.has(candidateCase.id)) {
|
|
8348
|
+
continue;
|
|
8349
|
+
}
|
|
8350
|
+
cases.push({
|
|
8351
|
+
id: candidateCase.id,
|
|
8352
|
+
outcome: "MISSING_FROM_BASELINE",
|
|
8353
|
+
candidate: candidateCase
|
|
8354
|
+
});
|
|
8355
|
+
}
|
|
8356
|
+
const baselinePassRate = passRate(baseline);
|
|
8357
|
+
const candidatePassRate = passRate(candidate);
|
|
8358
|
+
return {
|
|
8359
|
+
baselineLabel: labels?.baseline ?? "baseline",
|
|
8360
|
+
candidateLabel: labels?.candidate ?? candidate.metadata?.toolOverrideVariantId ?? "candidate",
|
|
8361
|
+
baselinePassRate,
|
|
8362
|
+
candidatePassRate,
|
|
8363
|
+
deltaPassRate: candidatePassRate - baselinePassRate,
|
|
8364
|
+
...metricDelta(
|
|
8365
|
+
"ToolPrecision",
|
|
8366
|
+
baseline.datasetToolPrecision,
|
|
8367
|
+
candidate.datasetToolPrecision
|
|
8368
|
+
),
|
|
8369
|
+
...metricDelta(
|
|
8370
|
+
"ToolRecall",
|
|
8371
|
+
baseline.datasetToolRecall,
|
|
8372
|
+
candidate.datasetToolRecall
|
|
8373
|
+
),
|
|
8374
|
+
...metricDelta("ToolF1", baseline.datasetToolF1, candidate.datasetToolF1),
|
|
8375
|
+
cases,
|
|
8376
|
+
improvedCases: cases.filter((c) => c.outcome === "IMPROVED"),
|
|
8377
|
+
regressedCases: cases.filter((c) => c.outcome === "REGRESSED"),
|
|
8378
|
+
unchangedPasses: cases.filter((c) => c.outcome === "UNCHANGED_PASS"),
|
|
8379
|
+
unchangedFailures: cases.filter((c) => c.outcome === "UNCHANGED_FAIL"),
|
|
8380
|
+
missingFromBaseline: cases.filter(
|
|
8381
|
+
(c) => c.outcome === "MISSING_FROM_BASELINE"
|
|
8382
|
+
),
|
|
8383
|
+
missingFromCandidate: cases.filter(
|
|
8384
|
+
(c) => c.outcome === "MISSING_FROM_CANDIDATE"
|
|
8385
|
+
)
|
|
8386
|
+
};
|
|
8387
|
+
}
|
|
8388
|
+
async function loadStoredEvalRunnerResult(storeLike, ref) {
|
|
8389
|
+
const store = resolveEvalResultStore(storeLike);
|
|
8390
|
+
const artifact = ref === "latest" ? await store.loadLatestArtifact("eval-runner-result") : await store.loadArtifact(
|
|
8391
|
+
"eval-runner-result",
|
|
8392
|
+
ref.id
|
|
8393
|
+
);
|
|
8394
|
+
if (!artifact) {
|
|
8395
|
+
throw new Error("No latest eval run artifact found");
|
|
8396
|
+
}
|
|
8397
|
+
return artifact;
|
|
8398
|
+
}
|
|
8399
|
+
async function saveEvalRunComparison(options) {
|
|
8400
|
+
const store = resolveEvalResultStore(options.store);
|
|
8401
|
+
const data = options.redactStoredResponses ? redactResponses2(options.comparison) : options.comparison;
|
|
8402
|
+
const artifact = createStoredEvalArtifact({
|
|
8403
|
+
kind: "eval-run-comparison",
|
|
8404
|
+
id: options.id,
|
|
8405
|
+
data,
|
|
8406
|
+
metadata: {
|
|
8407
|
+
labels: {
|
|
8408
|
+
baseline: options.comparison.baselineLabel,
|
|
8409
|
+
candidate: options.comparison.candidateLabel
|
|
8410
|
+
},
|
|
8411
|
+
...options.metadata ?? {}
|
|
8412
|
+
}
|
|
8413
|
+
});
|
|
8414
|
+
await store.saveArtifact(artifact);
|
|
8415
|
+
return artifact;
|
|
8416
|
+
}
|
|
8417
|
+
function compareCaseOutcome(baselinePass, candidatePass) {
|
|
8418
|
+
if (!baselinePass && candidatePass) return "IMPROVED";
|
|
8419
|
+
if (baselinePass && !candidatePass) return "REGRESSED";
|
|
8420
|
+
return baselinePass ? "UNCHANGED_PASS" : "UNCHANGED_FAIL";
|
|
8421
|
+
}
|
|
8422
|
+
function passRate(result) {
|
|
8423
|
+
return result.total > 0 ? result.passed / result.total : 0;
|
|
8424
|
+
}
|
|
8425
|
+
function metricDelta(name15, baselineValue, candidateValue) {
|
|
8426
|
+
const result = {};
|
|
8427
|
+
if (baselineValue !== void 0) {
|
|
8428
|
+
result[`baseline${name15}`] = baselineValue;
|
|
8429
|
+
}
|
|
8430
|
+
if (candidateValue !== void 0) {
|
|
8431
|
+
result[`candidate${name15}`] = candidateValue;
|
|
8432
|
+
}
|
|
8433
|
+
if (baselineValue !== void 0 && candidateValue !== void 0) {
|
|
8434
|
+
result[`delta${name15}`] = candidateValue - baselineValue;
|
|
8435
|
+
}
|
|
8436
|
+
return result;
|
|
8437
|
+
}
|
|
8438
|
+
function redactResponses2(value) {
|
|
8439
|
+
return JSON.parse(
|
|
8440
|
+
JSON.stringify(
|
|
8441
|
+
value,
|
|
8442
|
+
(key, currentValue) => key === "response" ? void 0 : currentValue
|
|
8443
|
+
)
|
|
8444
|
+
);
|
|
8445
|
+
}
|
|
8446
|
+
|
|
8447
|
+
// src/evals/variantExperiment.ts
|
|
8448
|
+
async function runVariantExperiment(options, context) {
|
|
8449
|
+
const metric = options.metric ?? "passRate";
|
|
8450
|
+
const maxRounds = options.maxRounds ?? 1;
|
|
8451
|
+
const minImprovement = options.minImprovement ?? 0;
|
|
8452
|
+
const allowRegressions = options.allowRegressions ?? false;
|
|
8453
|
+
const internalContext = {
|
|
8454
|
+
mcp: context.mcp,
|
|
8455
|
+
expect: context.expect
|
|
8456
|
+
};
|
|
8457
|
+
const baseline = await runEvalDataset(
|
|
8458
|
+
buildRunOptions(options, void 0),
|
|
8459
|
+
internalContext
|
|
8460
|
+
);
|
|
8461
|
+
const baselineValue = readMetric(baseline, metric);
|
|
8462
|
+
if (baselineValue === void 0) {
|
|
8463
|
+
throw new Error(
|
|
8464
|
+
`Metric '${metric}' is unavailable: the dataset produced no tool precision/recall data. Add mcp_host cases with toolsTriggered expectations, or use metric 'passRate'.`
|
|
8465
|
+
);
|
|
8466
|
+
}
|
|
8467
|
+
const rounds = [];
|
|
8468
|
+
let bestSoFar;
|
|
8469
|
+
let bestAttempted;
|
|
8470
|
+
let reason = "max-rounds";
|
|
8471
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
8472
|
+
const variants = await gatherVariants(options, {
|
|
8473
|
+
round,
|
|
8474
|
+
baseline,
|
|
8475
|
+
metric,
|
|
8476
|
+
history: rounds,
|
|
8477
|
+
bestSoFar
|
|
8478
|
+
});
|
|
8479
|
+
if (variants.length === 0) {
|
|
8480
|
+
reason = round === 0 ? "no-variants" : "no-improvement";
|
|
8481
|
+
break;
|
|
8482
|
+
}
|
|
8483
|
+
const candidates = [];
|
|
8484
|
+
for (const variant of variants) {
|
|
8485
|
+
const candidate = await scoreVariant(
|
|
8486
|
+
options,
|
|
8487
|
+
internalContext,
|
|
8488
|
+
baseline,
|
|
8489
|
+
baselineValue,
|
|
8490
|
+
metric,
|
|
8491
|
+
allowRegressions,
|
|
8492
|
+
variant
|
|
8493
|
+
);
|
|
8494
|
+
candidates.push(candidate);
|
|
8495
|
+
bestAttempted = pickBetter(bestAttempted, candidate, true);
|
|
8496
|
+
}
|
|
8497
|
+
const roundBest = candidates.reduce(
|
|
8498
|
+
(best, candidate) => pickBetter(best, candidate, false),
|
|
8499
|
+
void 0
|
|
8500
|
+
);
|
|
8501
|
+
rounds.push({ round, candidates, best: roundBest });
|
|
8502
|
+
if (roundBest) {
|
|
8503
|
+
const improvement = roundBest.metricValue - (bestSoFar?.metricValue ?? baselineValue);
|
|
8504
|
+
bestSoFar = pickBetter(bestSoFar, roundBest, false);
|
|
8505
|
+
if (improvement < minImprovement) {
|
|
8506
|
+
reason = "no-improvement";
|
|
8507
|
+
break;
|
|
8508
|
+
}
|
|
8509
|
+
}
|
|
8510
|
+
}
|
|
8511
|
+
const winner = bestSoFar;
|
|
8512
|
+
const proposalSource = winner ?? bestAttempted;
|
|
8513
|
+
const proposal = proposalSource ? buildProposal(metric, baselineValue, proposalSource, winner !== void 0) : void 0;
|
|
8514
|
+
const result = {
|
|
8515
|
+
metric,
|
|
8516
|
+
baseline,
|
|
8517
|
+
rounds,
|
|
8518
|
+
winner,
|
|
8519
|
+
proposal,
|
|
8520
|
+
converged: true,
|
|
8521
|
+
reason
|
|
8522
|
+
};
|
|
8523
|
+
if (context.testInfo) {
|
|
8524
|
+
const surfaceRun = winner?.result ?? bestAttempted?.result ?? baseline;
|
|
8525
|
+
await context.testInfo.attach("mcp-test-results", {
|
|
8526
|
+
contentType: "application/json",
|
|
8527
|
+
body: Buffer.from(
|
|
8528
|
+
JSON.stringify({ caseResults: surfaceRun.caseResults })
|
|
8529
|
+
)
|
|
8530
|
+
});
|
|
8531
|
+
await context.testInfo.attach("mcp-variant-experiment", {
|
|
8532
|
+
contentType: "application/json",
|
|
8533
|
+
body: Buffer.from(
|
|
8534
|
+
JSON.stringify(buildExperimentData(result, baselineValue))
|
|
8535
|
+
)
|
|
8536
|
+
});
|
|
8537
|
+
}
|
|
8538
|
+
return result;
|
|
8539
|
+
}
|
|
8540
|
+
function buildExperimentData(result, baselineValue) {
|
|
8541
|
+
return {
|
|
8542
|
+
metric: result.metric,
|
|
8543
|
+
baselineValue,
|
|
8544
|
+
bestValue: result.winner?.metricValue ?? result.proposal?.candidateValue ?? baselineValue,
|
|
8545
|
+
rounds: result.rounds.map((round) => {
|
|
8546
|
+
const best = round.best ?? round.candidates[0];
|
|
8547
|
+
return {
|
|
8548
|
+
round: round.round,
|
|
8549
|
+
variantId: best?.variant.id ?? "(none)",
|
|
8550
|
+
metricValue: best?.metricValue ?? baselineValue,
|
|
8551
|
+
metricDelta: best?.metricDelta ?? 0,
|
|
8552
|
+
disqualified: best?.disqualified ?? false
|
|
8553
|
+
};
|
|
8554
|
+
}),
|
|
8555
|
+
winnerVariantId: result.winner?.variant.id,
|
|
8556
|
+
recommendation: result.proposal?.recommendation,
|
|
8557
|
+
reason: result.reason
|
|
8558
|
+
};
|
|
8559
|
+
}
|
|
8560
|
+
async function gatherVariants(options, context) {
|
|
8561
|
+
if (context.round === 0 && options.variants && options.variants.length > 0) {
|
|
8562
|
+
return options.variants;
|
|
8563
|
+
}
|
|
8564
|
+
if (options.proposeVariants) {
|
|
8565
|
+
return options.proposeVariants(context);
|
|
8566
|
+
}
|
|
8567
|
+
return [];
|
|
8568
|
+
}
|
|
8569
|
+
async function scoreVariant(options, context, baseline, baselineValue, metric, allowRegressions, variant) {
|
|
8570
|
+
const result = await runEvalDataset(
|
|
8571
|
+
buildRunOptions(options, variant),
|
|
8572
|
+
context
|
|
8573
|
+
);
|
|
8574
|
+
const comparison = compareEvalRuns({
|
|
8575
|
+
baseline,
|
|
8576
|
+
candidate: result,
|
|
8577
|
+
labels: { candidate: variant.id }
|
|
8578
|
+
});
|
|
8579
|
+
const metricValue = readMetric(result, metric) ?? baselineValue;
|
|
8580
|
+
const disqualified = !allowRegressions && comparison.regressedCases.length > 0;
|
|
8581
|
+
return {
|
|
8582
|
+
variant,
|
|
8583
|
+
result,
|
|
8584
|
+
comparison,
|
|
8585
|
+
metricValue,
|
|
8586
|
+
metricDelta: metricValue - baselineValue,
|
|
8587
|
+
disqualified
|
|
8588
|
+
};
|
|
8589
|
+
}
|
|
8590
|
+
function pickBetter(incumbent, challenger, includeDisqualified) {
|
|
8591
|
+
if (!includeDisqualified && challenger.disqualified) {
|
|
8592
|
+
return incumbent;
|
|
8593
|
+
}
|
|
8594
|
+
if (!incumbent) {
|
|
8595
|
+
return challenger;
|
|
8596
|
+
}
|
|
8597
|
+
return challenger.metricValue > incumbent.metricValue ? challenger : incumbent;
|
|
8598
|
+
}
|
|
8599
|
+
function buildProposal(metric, baselineValue, source, isWinner) {
|
|
8600
|
+
let recommendation;
|
|
8601
|
+
if (isWinner) {
|
|
8602
|
+
recommendation = source.metricDelta > 0 ? "apply" : "inconclusive";
|
|
8603
|
+
} else {
|
|
8604
|
+
recommendation = source.disqualified ? "reject" : "inconclusive";
|
|
8605
|
+
}
|
|
8606
|
+
return {
|
|
8607
|
+
variantId: source.variant.id,
|
|
8608
|
+
metric,
|
|
8609
|
+
baselineValue,
|
|
8610
|
+
candidateValue: source.metricValue,
|
|
8611
|
+
delta: source.metricDelta,
|
|
8612
|
+
toolChanges: source.variant.tools,
|
|
8613
|
+
improvedCaseIds: source.comparison.improvedCases.map((c) => c.id),
|
|
8614
|
+
regressedCaseIds: source.comparison.regressedCases.map((c) => c.id),
|
|
8615
|
+
recommendation
|
|
8616
|
+
};
|
|
8617
|
+
}
|
|
8618
|
+
function readMetric(result, metric) {
|
|
8619
|
+
switch (metric) {
|
|
8620
|
+
case "passRate":
|
|
8621
|
+
return result.total > 0 ? result.passed / result.total : 0;
|
|
8622
|
+
case "toolF1":
|
|
8623
|
+
return result.datasetToolF1;
|
|
8624
|
+
case "toolPrecision":
|
|
8625
|
+
return result.datasetToolPrecision;
|
|
8626
|
+
case "toolRecall":
|
|
8627
|
+
return result.datasetToolRecall;
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8630
|
+
function buildRunOptions(options, toolOverrides) {
|
|
8631
|
+
return {
|
|
8632
|
+
dataset: options.dataset,
|
|
8633
|
+
toolOverrides,
|
|
8634
|
+
defaultLlmIterations: options.defaultLlmIterations,
|
|
8635
|
+
defaultJudgeReps: options.defaultJudgeReps,
|
|
8636
|
+
concurrency: options.concurrency,
|
|
8637
|
+
filterTags: options.filterTags,
|
|
8638
|
+
schemas: options.schemas,
|
|
8639
|
+
mcpHostModel: options.mcpHostModel,
|
|
8640
|
+
judgeModel: options.judgeModel
|
|
8641
|
+
};
|
|
7929
8642
|
}
|
|
7930
8643
|
|
|
7931
8644
|
// src/spec/conformanceChecks.ts
|
|
@@ -8105,15 +8818,22 @@ exports.DiscoveryError = DiscoveryError;
|
|
|
8105
8818
|
exports.ENV_VAR_NAMES = ENV_VAR_NAMES;
|
|
8106
8819
|
exports.EvalCaseSchema = EvalCaseSchema;
|
|
8107
8820
|
exports.EvalDatasetSchema = EvalDatasetSchema;
|
|
8821
|
+
exports.FileEvalResultStore = FileEvalResultStore;
|
|
8822
|
+
exports.GCSEvalResultStore = GCSEvalResultStore;
|
|
8108
8823
|
exports.MCPConfigSchema = MCPConfigSchema;
|
|
8109
8824
|
exports.MCP_PROTOCOL_VERSION = MCP_PROTOCOL_VERSION;
|
|
8110
8825
|
exports.SnapshotSanitizers = SnapshotSanitizers;
|
|
8111
8826
|
exports.clearJudgeRegistry = clearJudgeRegistry;
|
|
8112
8827
|
exports.closeMCPClient = closeMCPClient;
|
|
8828
|
+
exports.compareEvalRuns = compareEvalRuns;
|
|
8829
|
+
exports.createDefaultArtifactId = createDefaultArtifactId;
|
|
8830
|
+
exports.createEvalResultStore = createEvalResultStore;
|
|
8113
8831
|
exports.createJudge = createJudge;
|
|
8114
8832
|
exports.createMCPClientForConfig = createMCPClientForConfig;
|
|
8115
8833
|
exports.createMCPFixture = createMCPFixture;
|
|
8834
|
+
exports.createStoredEvalArtifact = createStoredEvalArtifact;
|
|
8116
8835
|
exports.createTokenAuthHeaders = createTokenAuthHeaders;
|
|
8836
|
+
exports.defaultEnvironmentMetadata = defaultEnvironmentMetadata;
|
|
8117
8837
|
exports.discoverAuthorizationServer = discoverAuthorizationServer;
|
|
8118
8838
|
exports.discoverProtectedResource = discoverProtectedResource;
|
|
8119
8839
|
exports.expect = expect;
|
|
@@ -8124,6 +8844,7 @@ exports.getResponseSizeBytes = getResponseSizeBytes;
|
|
|
8124
8844
|
exports.hasValidTokens = hasValidTokens;
|
|
8125
8845
|
exports.injectTokens = injectTokens;
|
|
8126
8846
|
exports.isBuiltInRubric = isBuiltInRubric;
|
|
8847
|
+
exports.isEvalResultStore = isEvalResultStore;
|
|
8127
8848
|
exports.isHttpConfig = isHttpConfig;
|
|
8128
8849
|
exports.isProviderAvailable = isProviderAvailable;
|
|
8129
8850
|
exports.isStdioConfig = isStdioConfig;
|
|
@@ -8132,6 +8853,7 @@ exports.isTokenExpiringSoon = isTokenExpiringSoon;
|
|
|
8132
8853
|
exports.loadBaseline = loadBaseline;
|
|
8133
8854
|
exports.loadEvalDataset = loadEvalDataset;
|
|
8134
8855
|
exports.loadEvalDatasetFromObject = loadEvalDatasetFromObject;
|
|
8856
|
+
exports.loadStoredEvalRunnerResult = loadStoredEvalRunnerResult;
|
|
8135
8857
|
exports.loadTokens = loadTokens;
|
|
8136
8858
|
exports.loadTokensFromEnv = loadTokensFromEnv;
|
|
8137
8859
|
exports.mcpAuthTest = test2;
|
|
@@ -8142,12 +8864,16 @@ exports.performOAuthSetup = performOAuthSetup;
|
|
|
8142
8864
|
exports.performOAuthSetupIfNeeded = performOAuthSetupIfNeeded;
|
|
8143
8865
|
exports.refreshAccessToken = refreshAccessToken;
|
|
8144
8866
|
exports.registerJudge = registerJudge;
|
|
8867
|
+
exports.resolveEvalResultStore = resolveEvalResultStore;
|
|
8145
8868
|
exports.resolveRubric = resolveRubric;
|
|
8146
8869
|
exports.runConformanceChecks = runConformanceChecks;
|
|
8147
8870
|
exports.runEvalCase = runEvalCase;
|
|
8148
8871
|
exports.runEvalDataset = runEvalDataset;
|
|
8149
8872
|
exports.runServerComparison = runServerComparison;
|
|
8873
|
+
exports.runVariantExperiment = runVariantExperiment;
|
|
8150
8874
|
exports.saveBaseline = saveBaseline;
|
|
8875
|
+
exports.saveEvalRunComparison = saveEvalRunComparison;
|
|
8876
|
+
exports.saveServerComparison = saveServerComparison;
|
|
8151
8877
|
exports.simulateMCPHost = simulateMCPHost;
|
|
8152
8878
|
exports.test = test;
|
|
8153
8879
|
exports.validateAccessToken = validateAccessToken;
|