@gleanwork/mcp-server-tester 1.1.0 → 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.
@@ -2087,6 +2087,8 @@ interface LLMToolCall {
2087
2087
  arguments: Record<string, unknown>;
2088
2088
  /** Optional tool call ID (for tracking) */
2089
2089
  id?: string;
2090
+ /** Tool result text, when the host surfaces it (paired to this call) */
2091
+ output?: string;
2090
2092
  }
2091
2093
  /**
2092
2094
  * Result from an MCP host simulation
@@ -2102,10 +2104,17 @@ interface MCPHostSimulationResult {
2102
2104
  error?: string;
2103
2105
  /** The scenario prompt that was given to the LLM */
2104
2106
  scenario?: string;
2105
- /** The conversation turns for attribution analysis */
2107
+ /**
2108
+ * The conversation turns for attribution analysis.
2109
+ *
2110
+ * Tool turns reference their call via `toolCallId` rather than inlining the
2111
+ * (potentially large) result — hydrate the output from the matching
2112
+ * `toolCalls[]` entry. `content` holds assistant/user text.
2113
+ */
2106
2114
  conversationHistory?: Array<{
2107
2115
  role: 'user' | 'assistant' | 'tool';
2108
- content: string;
2116
+ content?: string;
2117
+ toolCallId?: string;
2109
2118
  }>;
2110
2119
  /**
2111
2120
  * Milliseconds spent waiting for LLM responses
@@ -3272,6 +3281,37 @@ interface MCPEvalRunData {
3272
3281
  * Server capabilities discovered via listTools (optional)
3273
3282
  */
3274
3283
  serverCapabilities?: MCPServerCapabilitiesData[];
3284
+ /**
3285
+ * Summary of a tool-metadata variant experiment (runVariantExperiment),
3286
+ * present when the run was produced by one. The `results` above reflect the
3287
+ * winning variant; this records how the experiment got there.
3288
+ */
3289
+ variantExperiment?: MCPVariantExperimentData;
3290
+ }
3291
+ /**
3292
+ * Compact summary of a `runVariantExperiment` run, for the reporter UI.
3293
+ */
3294
+ interface MCPVariantExperimentData {
3295
+ /** Metric optimized: passRate | toolF1 | toolPrecision | toolRecall. */
3296
+ metric: string;
3297
+ /** Baseline metric value (0-1), before any variant. */
3298
+ baselineValue: number;
3299
+ /** Best metric value achieved (the winner, or best attempt) (0-1). */
3300
+ bestValue: number;
3301
+ /** The best candidate from each round, in order. */
3302
+ rounds: Array<{
3303
+ round: number;
3304
+ variantId: string;
3305
+ metricValue: number;
3306
+ metricDelta: number;
3307
+ disqualified: boolean;
3308
+ }>;
3309
+ /** Winning variant id, if a non-regressing candidate beat the baseline. */
3310
+ winnerVariantId?: string;
3311
+ /** apply | reject | inconclusive */
3312
+ recommendation?: string;
3313
+ /** Why the experiment stopped. */
3314
+ reason: string;
3275
3315
  }
3276
3316
  /**
3277
3317
  * Historical summary for trend charts
@@ -2087,6 +2087,8 @@ interface LLMToolCall {
2087
2087
  arguments: Record<string, unknown>;
2088
2088
  /** Optional tool call ID (for tracking) */
2089
2089
  id?: string;
2090
+ /** Tool result text, when the host surfaces it (paired to this call) */
2091
+ output?: string;
2090
2092
  }
2091
2093
  /**
2092
2094
  * Result from an MCP host simulation
@@ -2102,10 +2104,17 @@ interface MCPHostSimulationResult {
2102
2104
  error?: string;
2103
2105
  /** The scenario prompt that was given to the LLM */
2104
2106
  scenario?: string;
2105
- /** The conversation turns for attribution analysis */
2107
+ /**
2108
+ * The conversation turns for attribution analysis.
2109
+ *
2110
+ * Tool turns reference their call via `toolCallId` rather than inlining the
2111
+ * (potentially large) result — hydrate the output from the matching
2112
+ * `toolCalls[]` entry. `content` holds assistant/user text.
2113
+ */
2106
2114
  conversationHistory?: Array<{
2107
2115
  role: 'user' | 'assistant' | 'tool';
2108
- content: string;
2116
+ content?: string;
2117
+ toolCallId?: string;
2109
2118
  }>;
2110
2119
  /**
2111
2120
  * Milliseconds spent waiting for LLM responses
@@ -3272,6 +3281,37 @@ interface MCPEvalRunData {
3272
3281
  * Server capabilities discovered via listTools (optional)
3273
3282
  */
3274
3283
  serverCapabilities?: MCPServerCapabilitiesData[];
3284
+ /**
3285
+ * Summary of a tool-metadata variant experiment (runVariantExperiment),
3286
+ * present when the run was produced by one. The `results` above reflect the
3287
+ * winning variant; this records how the experiment got there.
3288
+ */
3289
+ variantExperiment?: MCPVariantExperimentData;
3290
+ }
3291
+ /**
3292
+ * Compact summary of a `runVariantExperiment` run, for the reporter UI.
3293
+ */
3294
+ interface MCPVariantExperimentData {
3295
+ /** Metric optimized: passRate | toolF1 | toolPrecision | toolRecall. */
3296
+ metric: string;
3297
+ /** Baseline metric value (0-1), before any variant. */
3298
+ baselineValue: number;
3299
+ /** Best metric value achieved (the winner, or best attempt) (0-1). */
3300
+ bestValue: number;
3301
+ /** The best candidate from each round, in order. */
3302
+ rounds: Array<{
3303
+ round: number;
3304
+ variantId: string;
3305
+ metricValue: number;
3306
+ metricDelta: number;
3307
+ disqualified: boolean;
3308
+ }>;
3309
+ /** Winning variant id, if a non-regressing candidate beat the baseline. */
3310
+ winnerVariantId?: string;
3311
+ /** apply | reject | inconclusive */
3312
+ recommendation?: string;
3313
+ /** Why the experiment stopped. */
3314
+ reason: string;
3275
3315
  }
3276
3316
  /**
3277
3317
  * Historical summary for trend charts
package/dist/index.cjs CHANGED
@@ -4425,7 +4425,7 @@ function escapeHtml(text) {
4425
4425
 
4426
4426
  // package.json
4427
4427
  var package_default = {
4428
- version: "1.1.0"};
4428
+ version: "1.1.1"};
4429
4429
 
4430
4430
  // src/mcp/clientFactory.ts
4431
4431
  function getRetryAfterDelayMs(err) {
@@ -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: model not found.
6842
- Hint: check the model name format for your provider. For vertex-anthropic use 'claude-3-5-haiku@20241022' (with @).`;
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
- allToolCalls.push({ name: toolName, arguments: args });
6945
- return extractText(result2);
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 ?? []).map((step) => ({
6968
- role: step.toolCalls?.length > 0 ? "tool" : "assistant",
6969
- content: step.toolCalls?.length > 0 ? JSON.stringify(step.toolResults) : step.text ?? ""
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,
@@ -7023,7 +7036,13 @@ function parseStreamJson(stdout) {
7023
7036
  for (const block of event.message.content) {
7024
7037
  if (block.type === "tool_result") {
7025
7038
  const content = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
7026
- conversationHistory.push({ role: "tool", content });
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
+ }
7027
7046
  }
7028
7047
  }
7029
7048
  }
@@ -7979,6 +7998,7 @@ async function getGitHash() {
7979
7998
  const result = await execFileNoThrow("git", ["rev-parse", "HEAD"]);
7980
7999
  return result.status === 0 ? result.stdout.trim() : void 0;
7981
8000
  }
8001
+ var warnedNoTestInfo = false;
7982
8002
  async function runEvalDataset(options, context) {
7983
8003
  const {
7984
8004
  dataset,
@@ -8149,7 +8169,8 @@ async function runEvalDataset(options, context) {
8149
8169
  contentType: "application/json",
8150
8170
  body: Buffer.from(JSON.stringify({ caseResults }))
8151
8171
  });
8152
- } else if (caseResults.length > 0) {
8172
+ } else if (caseResults.length > 0 && !warnedNoTestInfo) {
8173
+ warnedNoTestInfo = true;
8153
8174
  console.warn(
8154
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 });"
8155
8176
  );
@@ -8429,9 +8450,13 @@ async function runVariantExperiment(options, context) {
8429
8450
  const maxRounds = options.maxRounds ?? 1;
8430
8451
  const minImprovement = options.minImprovement ?? 0;
8431
8452
  const allowRegressions = options.allowRegressions ?? false;
8453
+ const internalContext = {
8454
+ mcp: context.mcp,
8455
+ expect: context.expect
8456
+ };
8432
8457
  const baseline = await runEvalDataset(
8433
8458
  buildRunOptions(options, void 0),
8434
- context
8459
+ internalContext
8435
8460
  );
8436
8461
  const baselineValue = readMetric(baseline, metric);
8437
8462
  if (baselineValue === void 0) {
@@ -8459,7 +8484,7 @@ async function runVariantExperiment(options, context) {
8459
8484
  for (const variant of variants) {
8460
8485
  const candidate = await scoreVariant(
8461
8486
  options,
8462
- context,
8487
+ internalContext,
8463
8488
  baseline,
8464
8489
  baselineValue,
8465
8490
  metric,
@@ -8486,7 +8511,7 @@ async function runVariantExperiment(options, context) {
8486
8511
  const winner = bestSoFar;
8487
8512
  const proposalSource = winner ?? bestAttempted;
8488
8513
  const proposal = proposalSource ? buildProposal(metric, baselineValue, proposalSource, winner !== void 0) : void 0;
8489
- return {
8514
+ const result = {
8490
8515
  metric,
8491
8516
  baseline,
8492
8517
  rounds,
@@ -8495,6 +8520,42 @@ async function runVariantExperiment(options, context) {
8495
8520
  converged: true,
8496
8521
  reason
8497
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
+ };
8498
8559
  }
8499
8560
  async function gatherVariants(options, context) {
8500
8561
  if (context.round === 0 && options.variants && options.variants.length > 0) {