@gleanwork/mcp-server-tester 2.0.0-beta.3 → 2.0.0-beta.4

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 CHANGED
@@ -3608,22 +3608,22 @@ async function runAnthropicComputerUseHitl(options) {
3608
3608
  "HITL check"
3609
3609
  );
3610
3610
  }
3611
- async function runComputerUseDriver(query2, options, mode, label) {
3611
+ async function runComputerUseDriver(query2, options, mode, label2) {
3612
3612
  const env = {
3613
3613
  ...process.env,
3614
3614
  ...options.env,
3615
3615
  ...options.model ? { MST_COWORK_CUA_MODEL: options.model } : {}
3616
3616
  };
3617
3617
  if (options.deadlineAt <= Date.now())
3618
- throw new Error(`Computer Use ${label} deadline exceeded; not retrying.`);
3618
+ throw new Error(`Computer Use ${label2} deadline exceeded; not retrying.`);
3619
3619
  const python = await ensureCoworkPython(env);
3620
3620
  const DRIVER_PATH = resolveDriverPath(env);
3621
3621
  const timeoutMs = options.deadlineAt - Date.now();
3622
3622
  if (timeoutMs <= 0)
3623
- throw new Error(`Computer Use ${label} deadline exceeded; not retrying.`);
3623
+ throw new Error(`Computer Use ${label2} deadline exceeded; not retrying.`);
3624
3624
  const maxActions = options.maxActions ?? (mode === "hitl" ? 12 : 24);
3625
3625
  diagnostic(
3626
- `starting Computer Use ${label} (script=${DRIVER_PATH}, maxActions=${maxActions}, timeoutMs=${timeoutMs})`
3626
+ `starting Computer Use ${label2} (script=${DRIVER_PATH}, maxActions=${maxActions}, timeoutMs=${timeoutMs})`
3627
3627
  );
3628
3628
  let stdout = "";
3629
3629
  try {
@@ -3654,26 +3654,26 @@ async function runComputerUseDriver(query2, options, mode, label) {
3654
3654
  telemetry3
3655
3655
  );
3656
3656
  }
3657
- diagnostic(`Computer Use ${label} failed: ${details}`);
3657
+ diagnostic(`Computer Use ${label2} failed: ${details}`);
3658
3658
  throw new ComputerUseDriverError(
3659
- `Computer Use ${label} failed: ${details}`,
3659
+ `Computer Use ${label2} failed: ${details}`,
3660
3660
  telemetry3
3661
3661
  );
3662
3662
  }
3663
3663
  diagnostic(
3664
- `Computer Use ${label} exited successfully (stdoutBytes=${stdout.length})`
3664
+ `Computer Use ${label2} exited successfully (stdoutBytes=${stdout.length})`
3665
3665
  );
3666
3666
  const record = parseLastJsonLine(stdout);
3667
3667
  const expectedStatus = mode === "submit" ? "submitted" : "hitl_checked";
3668
3668
  if (record?.status !== expectedStatus) {
3669
3669
  throw new ComputerUseDriverError(
3670
- `Computer Use ${label} did not reach ${expectedStatus}.`,
3670
+ `Computer Use ${label2} did not reach ${expectedStatus}.`,
3671
3671
  parseTelemetry(record?.telemetry, env, "partial")
3672
3672
  );
3673
3673
  }
3674
3674
  if (!isCount(record.action_count) || !isSafeModel(record.model, env)) {
3675
3675
  throw new ComputerUseDriverError(
3676
- `Computer Use ${label} returned invalid result fields.`,
3676
+ `Computer Use ${label2} returned invalid result fields.`,
3677
3677
  parseTelemetry(record.telemetry, env, "partial")
3678
3678
  );
3679
3679
  }
@@ -3684,7 +3684,7 @@ async function runComputerUseDriver(query2, options, mode, label) {
3684
3684
  ...telemetry2 ? { telemetry: telemetry2 } : {}
3685
3685
  };
3686
3686
  diagnostic(
3687
- `Computer Use ${label} completed (actions=${common.action_count})`
3687
+ `Computer Use ${label2} completed (actions=${common.action_count})`
3688
3688
  );
3689
3689
  if (mode === "hitl") return { status: "hitl_checked", ...common };
3690
3690
  const submission = asRecord(record.submission_action);
@@ -5563,12 +5563,12 @@ init_esm_shims();
5563
5563
 
5564
5564
  // src/cli/components/Spinner.tsx
5565
5565
  init_esm_shims();
5566
- function Spinner({ label }) {
5566
+ function Spinner({ label: label2 }) {
5567
5567
  return /* @__PURE__ */ jsxs(Box, { children: [
5568
5568
  /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(InkSpinner, { type: "dots" }) }),
5569
5569
  /* @__PURE__ */ jsxs(Text, { children: [
5570
5570
  " ",
5571
- label
5571
+ label2
5572
5572
  ] })
5573
5573
  ] });
5574
5574
  }
@@ -5630,7 +5630,7 @@ init_esm_shims();
5630
5630
 
5631
5631
  // package.json
5632
5632
  var package_default = {
5633
- version: "2.0.0-beta.3"};
5633
+ version: "2.0.0-beta.4"};
5634
5634
 
5635
5635
  // src/cli/templates/index.ts
5636
5636
  function getPlaywrightConfigTemplate(answers) {
@@ -6362,8 +6362,56 @@ async function performClientCredentialsFlow(config) {
6362
6362
  };
6363
6363
  }
6364
6364
 
6365
+ // src/mcp/connectionDiagnostics.ts
6366
+ init_esm_shims();
6367
+ function classifyMCPConnectionFailure(error) {
6368
+ if (typeof error !== "object" || error === null) return "connection_failed";
6369
+ const details = error;
6370
+ const status = details.response?.status ?? details.status ?? details.code;
6371
+ if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) {
6372
+ return `http_${status}`;
6373
+ }
6374
+ const message = error instanceof Error ? error.message : "";
6375
+ const httpStatus = /\b(?:HTTP|status(?: code)?|code:)\s*[:=(]?\s*([45]\d{2})\b/i.exec(
6376
+ message
6377
+ )?.[1];
6378
+ if (httpStatus) return `http_${httpStatus}`;
6379
+ for (const code of ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND"]) {
6380
+ if (details.code === code || message.toUpperCase().includes(code)) {
6381
+ return code.toLowerCase();
6382
+ }
6383
+ }
6384
+ if (/timed out|timeout/i.test(message)) return "timeout";
6385
+ if (/network|socket hang up|fetch failed/i.test(message))
6386
+ return "network_error";
6387
+ return "connection_failed";
6388
+ }
6389
+ var MCPHttpConnectionError = class extends Error {
6390
+ constructor(streamableError, sseError, retryable, retryAfterMs) {
6391
+ const streamableHttpFailure = classifyMCPConnectionFailure(streamableError);
6392
+ const sseFailure = classifyMCPConnectionFailure(sseError);
6393
+ super(
6394
+ `MCP connection failed: streamableHttp=${streamableHttpFailure}; sse=${sseFailure}`
6395
+ );
6396
+ this.retryable = retryable;
6397
+ this.retryAfterMs = retryAfterMs;
6398
+ this.name = "MCPHttpConnectionError";
6399
+ this.streamableHttpFailure = streamableHttpFailure;
6400
+ this.sseFailure = sseFailure;
6401
+ }
6402
+ streamableHttpFailure;
6403
+ sseFailure;
6404
+ };
6405
+ function formatMCPConnectionFailure(error) {
6406
+ if (error instanceof MCPHttpConnectionError) {
6407
+ return `MCP connection failed: streamableHttp=${error.streamableHttpFailure}; sse=${error.sseFailure}`;
6408
+ }
6409
+ return classifyMCPConnectionFailure(error);
6410
+ }
6411
+
6365
6412
  // src/mcp/clientFactory.ts
6366
6413
  function getRetryAfterDelayMs(err) {
6414
+ if (err instanceof MCPHttpConnectionError) return err.retryAfterMs;
6367
6415
  const response = err?.response;
6368
6416
  const retryAfter = response?.headers?.get?.("Retry-After");
6369
6417
  if (retryAfter) {
@@ -6382,6 +6430,7 @@ function isTransientNetworkError(err) {
6382
6430
  return msg.includes("econnreset") || msg.includes("econnrefused") || msg.includes("etimedout") || msg.includes("enotfound") || msg.includes("network") || msg.includes("socket hang up") || msg.includes("fetch failed");
6383
6431
  }
6384
6432
  function isRetryableError(err) {
6433
+ if (err instanceof MCPHttpConnectionError) return err.retryable;
6385
6434
  return isTransientNetworkError(err) || isRateLimitError(err);
6386
6435
  }
6387
6436
  async function retryWithBackoff(fn, maxAttempts) {
@@ -6399,7 +6448,7 @@ async function retryWithBackoff(fn, maxAttempts) {
6399
6448
  attempt + 1,
6400
6449
  maxAttempts + 1,
6401
6450
  delayMs,
6402
- err.message
6451
+ formatMCPConnectionFailure(err)
6403
6452
  );
6404
6453
  await new Promise((resolve10) => setTimeout(resolve10, delayMs));
6405
6454
  } else {
@@ -6556,15 +6605,24 @@ async function createMCPClientForConfig(config, options) {
6556
6605
  } catch (err) {
6557
6606
  debugHttp(
6558
6607
  "streamableHttp failed (%s), falling back to SSE",
6559
- err.message
6608
+ formatMCPConnectionFailure(err)
6560
6609
  );
6561
6610
  debugClient("Streamable HTTP failed, falling back to SSE transport");
6562
6611
  debugHttp("Attempting transport: sse");
6563
- const sseTransport = new SSEClientTransport(url, {
6564
- requestInit,
6565
- ...options?.authProvider ? { authProvider: options.authProvider } : {}
6566
- });
6567
- await client.connect(sseTransport, connectOptions);
6612
+ try {
6613
+ const sseTransport = new SSEClientTransport(url, {
6614
+ requestInit,
6615
+ ...options?.authProvider ? { authProvider: options.authProvider } : {}
6616
+ });
6617
+ await client.connect(sseTransport, connectOptions);
6618
+ } catch (sseError) {
6619
+ throw new MCPHttpConnectionError(
6620
+ err,
6621
+ sseError,
6622
+ isRetryableError(err) || isRetryableError(sseError),
6623
+ getRetryAfterDelayMs(err) ?? getRetryAfterDelayMs(sseError)
6624
+ );
6625
+ }
6568
6626
  debugClient("Connected via SSE");
6569
6627
  debugHttp("Connection established via sse");
6570
6628
  }
@@ -10830,6 +10888,87 @@ async function getCoworkPlatform(provider) {
10830
10888
  );
10831
10889
  }
10832
10890
 
10891
+ // src/evals/cowork/mcpReadiness.ts
10892
+ init_esm_shims();
10893
+ init_config();
10894
+ var PREFLIGHT_TIMEOUT_MS = 3e4;
10895
+ var CoworkMcpReadinessError = class extends Error {
10896
+ servers;
10897
+ constructor(servers) {
10898
+ super(
10899
+ `Cowork MCP preflight failed; no task was submitted. ${servers.map(
10900
+ (server) => `${server.label}=${server.status}${server.error ? `(${server.error})` : ""}(${server.elapsedMs}ms)`
10901
+ ).join(", ")}`
10902
+ );
10903
+ this.name = "CoworkMcpReadinessError";
10904
+ this.servers = servers;
10905
+ }
10906
+ };
10907
+ function label(server, index) {
10908
+ return server.label ?? `server-${index + 1}`;
10909
+ }
10910
+ async function withTimeout(promise, timeoutMs) {
10911
+ let timer;
10912
+ try {
10913
+ return await Promise.race([
10914
+ promise,
10915
+ new Promise((_, reject) => {
10916
+ timer = setTimeout(
10917
+ () => reject(new Error("MCP preflight timed out")),
10918
+ timeoutMs
10919
+ );
10920
+ })
10921
+ ]);
10922
+ } finally {
10923
+ if (timer) clearTimeout(timer);
10924
+ }
10925
+ }
10926
+ function resolveServer(server, env) {
10927
+ if (!isHttpConfig(server)) return server;
10928
+ const [coworkServer] = toCoworkServers([server]);
10929
+ if (!coworkServer) throw new Error("Invalid Cowork MCP configuration.");
10930
+ const headers = resolveCoworkMcpHeaders([coworkServer], env);
10931
+ return {
10932
+ ...server,
10933
+ // Use the same validated runtime headers as Cowork setup.
10934
+ auth: void 0,
10935
+ headers: headers[coworkServer.label]
10936
+ };
10937
+ }
10938
+ async function verifyCoworkMcpServers(servers, env) {
10939
+ const results = await Promise.all(
10940
+ servers.map(async (server, index) => {
10941
+ const started = Date.now();
10942
+ let client;
10943
+ try {
10944
+ client = await createMCPClientForConfig(resolveServer(server, env));
10945
+ const tools = await withTimeout(
10946
+ client.listTools(),
10947
+ PREFLIGHT_TIMEOUT_MS
10948
+ );
10949
+ return {
10950
+ label: label(server, index),
10951
+ status: "connected",
10952
+ toolCount: tools.tools.length,
10953
+ elapsedMs: Date.now() - started
10954
+ };
10955
+ } catch (error) {
10956
+ return {
10957
+ label: label(server, index),
10958
+ status: "failed",
10959
+ elapsedMs: Date.now() - started,
10960
+ error: formatMCPConnectionFailure(error)
10961
+ };
10962
+ } finally {
10963
+ if (client) await closeMCPClient(client).catch(() => void 0);
10964
+ }
10965
+ })
10966
+ );
10967
+ if (results.some((result) => result.status !== "connected"))
10968
+ throw new CoworkMcpReadinessError(results);
10969
+ return results;
10970
+ }
10971
+
10833
10972
  // src/evals/externalHost/builtins/anthropicClaude.ts
10834
10973
  init_esm_shims();
10835
10974
 
@@ -12387,6 +12526,14 @@ async function runBatch(requests, context, selectedPlatform) {
12387
12526
  env,
12388
12527
  model: config.model
12389
12528
  });
12529
+ if (servers.length) {
12530
+ const readiness = await verifyCoworkMcpServers(servers, env);
12531
+ process.stderr.write(
12532
+ `[mst:cowork] MCP preflight ready: ${readiness.map(
12533
+ (server) => `${server.label}(${server.toolCount ?? 0} tools, ${server.elapsedMs}ms)`
12534
+ ).join(", ")}\\n`
12535
+ );
12536
+ }
12390
12537
  for (const [index, request] of requests.entries()) {
12391
12538
  const caseStartedAt = Date.now();
12392
12539
  const computerUse = {
@@ -12888,8 +13035,8 @@ function claudeCliHost(options) {
12888
13035
  if (options.servers) {
12889
13036
  for (const key of Object.keys(mcpServers)) delete mcpServers[key];
12890
13037
  for (const entry of options.servers) {
12891
- const label = entry.label ?? "mcp-server";
12892
- mcpServers[label] = entry.transport === "http" ? {
13038
+ const label2 = entry.label ?? "mcp-server";
13039
+ mcpServers[label2] = entry.transport === "http" ? {
12893
13040
  type: "http",
12894
13041
  url: entry.serverUrl,
12895
13042
  headers: {
@@ -16320,9 +16467,9 @@ function buildArmDeltas(arms) {
16320
16467
  );
16321
16468
  }
16322
16469
  function redactServerForReport(server) {
16323
- const label = server.label ? { label: server.label } : {};
16470
+ const label2 = server.label ? { label: server.label } : {};
16324
16471
  if (server.transport === "stdio") {
16325
- return { transport: "stdio", command: server.command, ...label };
16472
+ return { transport: "stdio", command: server.command, ...label2 };
16326
16473
  }
16327
16474
  const url = new URL(server.serverUrl);
16328
16475
  url.username = "";
@@ -16332,7 +16479,7 @@ function redactServerForReport(server) {
16332
16479
  return {
16333
16480
  transport: "http",
16334
16481
  serverUrl: url.toString(),
16335
- ...label,
16482
+ ...label2,
16336
16483
  ...server.auth?.accessTokenEnv ? { auth: { accessTokenEnv: server.auth.accessTokenEnv } } : {}
16337
16484
  };
16338
16485
  }
@@ -1874,7 +1874,7 @@ var debugHttp = createDebug(`${NAMESPACE}:http`);
1874
1874
 
1875
1875
  // package.json
1876
1876
  var package_default = {
1877
- version: "2.0.0-beta.3"};
1877
+ version: "2.0.0-beta.4"};
1878
1878
  var debug = createDebug("mcp-server-tester:oauth-flow");
1879
1879
  async function generatePKCE() {
1880
1880
  const codeVerifier = oauth.generateRandomCodeVerifier();
@@ -2038,8 +2038,55 @@ async function performClientCredentialsFlow(config) {
2038
2038
  };
2039
2039
  }
2040
2040
 
2041
+ // src/mcp/connectionDiagnostics.ts
2042
+ function classifyMCPConnectionFailure(error) {
2043
+ if (typeof error !== "object" || error === null) return "connection_failed";
2044
+ const details = error;
2045
+ const status = details.response?.status ?? details.status ?? details.code;
2046
+ if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) {
2047
+ return `http_${status}`;
2048
+ }
2049
+ const message = error instanceof Error ? error.message : "";
2050
+ const httpStatus = /\b(?:HTTP|status(?: code)?|code:)\s*[:=(]?\s*([45]\d{2})\b/i.exec(
2051
+ message
2052
+ )?.[1];
2053
+ if (httpStatus) return `http_${httpStatus}`;
2054
+ for (const code of ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND"]) {
2055
+ if (details.code === code || message.toUpperCase().includes(code)) {
2056
+ return code.toLowerCase();
2057
+ }
2058
+ }
2059
+ if (/timed out|timeout/i.test(message)) return "timeout";
2060
+ if (/network|socket hang up|fetch failed/i.test(message))
2061
+ return "network_error";
2062
+ return "connection_failed";
2063
+ }
2064
+ var MCPHttpConnectionError = class extends Error {
2065
+ constructor(streamableError, sseError, retryable, retryAfterMs) {
2066
+ const streamableHttpFailure = classifyMCPConnectionFailure(streamableError);
2067
+ const sseFailure = classifyMCPConnectionFailure(sseError);
2068
+ super(
2069
+ `MCP connection failed: streamableHttp=${streamableHttpFailure}; sse=${sseFailure}`
2070
+ );
2071
+ this.retryable = retryable;
2072
+ this.retryAfterMs = retryAfterMs;
2073
+ this.name = "MCPHttpConnectionError";
2074
+ this.streamableHttpFailure = streamableHttpFailure;
2075
+ this.sseFailure = sseFailure;
2076
+ }
2077
+ streamableHttpFailure;
2078
+ sseFailure;
2079
+ };
2080
+ function formatMCPConnectionFailure(error) {
2081
+ if (error instanceof MCPHttpConnectionError) {
2082
+ return `MCP connection failed: streamableHttp=${error.streamableHttpFailure}; sse=${error.sseFailure}`;
2083
+ }
2084
+ return classifyMCPConnectionFailure(error);
2085
+ }
2086
+
2041
2087
  // src/mcp/clientFactory.ts
2042
2088
  function getRetryAfterDelayMs(err) {
2089
+ if (err instanceof MCPHttpConnectionError) return err.retryAfterMs;
2043
2090
  const response = err?.response;
2044
2091
  const retryAfter = response?.headers?.get?.("Retry-After");
2045
2092
  if (retryAfter) {
@@ -2058,6 +2105,7 @@ function isTransientNetworkError(err) {
2058
2105
  return msg.includes("econnreset") || msg.includes("econnrefused") || msg.includes("etimedout") || msg.includes("enotfound") || msg.includes("network") || msg.includes("socket hang up") || msg.includes("fetch failed");
2059
2106
  }
2060
2107
  function isRetryableError(err) {
2108
+ if (err instanceof MCPHttpConnectionError) return err.retryable;
2061
2109
  return isTransientNetworkError(err) || isRateLimitError(err);
2062
2110
  }
2063
2111
  async function retryWithBackoff(fn, maxAttempts) {
@@ -2075,7 +2123,7 @@ async function retryWithBackoff(fn, maxAttempts) {
2075
2123
  attempt + 1,
2076
2124
  maxAttempts + 1,
2077
2125
  delayMs,
2078
- err.message
2126
+ formatMCPConnectionFailure(err)
2079
2127
  );
2080
2128
  await new Promise((resolve) => setTimeout(resolve, delayMs));
2081
2129
  } else {
@@ -2232,15 +2280,24 @@ async function createMCPClientForConfig(config, options) {
2232
2280
  } catch (err) {
2233
2281
  debugHttp(
2234
2282
  "streamableHttp failed (%s), falling back to SSE",
2235
- err.message
2283
+ formatMCPConnectionFailure(err)
2236
2284
  );
2237
2285
  debugClient("Streamable HTTP failed, falling back to SSE transport");
2238
2286
  debugHttp("Attempting transport: sse");
2239
- const sseTransport = new SSEClientTransport(url, {
2240
- requestInit,
2241
- ...options?.authProvider ? { authProvider: options.authProvider } : {}
2242
- });
2243
- await client.connect(sseTransport, connectOptions);
2287
+ try {
2288
+ const sseTransport = new SSEClientTransport(url, {
2289
+ requestInit,
2290
+ ...options?.authProvider ? { authProvider: options.authProvider } : {}
2291
+ });
2292
+ await client.connect(sseTransport, connectOptions);
2293
+ } catch (sseError) {
2294
+ throw new MCPHttpConnectionError(
2295
+ err,
2296
+ sseError,
2297
+ isRetryableError(err) || isRetryableError(sseError),
2298
+ getRetryAfterDelayMs(err) ?? getRetryAfterDelayMs(sseError)
2299
+ );
2300
+ }
2244
2301
  debugClient("Connected via SSE");
2245
2302
  debugHttp("Connection established via sse");
2246
2303
  }