@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/index.js CHANGED
@@ -3836,22 +3836,22 @@ async function runAnthropicComputerUseHitl(options) {
3836
3836
  "HITL check"
3837
3837
  );
3838
3838
  }
3839
- async function runComputerUseDriver(query2, options, mode, label) {
3839
+ async function runComputerUseDriver(query2, options, mode, label2) {
3840
3840
  const env = {
3841
3841
  ...process.env,
3842
3842
  ...options.env,
3843
3843
  ...options.model ? { MST_COWORK_CUA_MODEL: options.model } : {}
3844
3844
  };
3845
3845
  if (options.deadlineAt <= Date.now())
3846
- throw new Error(`Computer Use ${label} deadline exceeded; not retrying.`);
3846
+ throw new Error(`Computer Use ${label2} deadline exceeded; not retrying.`);
3847
3847
  const python = await ensureCoworkPython(env);
3848
3848
  const DRIVER_PATH = resolveDriverPath(env);
3849
3849
  const timeoutMs = options.deadlineAt - Date.now();
3850
3850
  if (timeoutMs <= 0)
3851
- throw new Error(`Computer Use ${label} deadline exceeded; not retrying.`);
3851
+ throw new Error(`Computer Use ${label2} deadline exceeded; not retrying.`);
3852
3852
  const maxActions = options.maxActions ?? (mode === "hitl" ? 12 : 24);
3853
3853
  diagnostic(
3854
- `starting Computer Use ${label} (script=${DRIVER_PATH}, maxActions=${maxActions}, timeoutMs=${timeoutMs})`
3854
+ `starting Computer Use ${label2} (script=${DRIVER_PATH}, maxActions=${maxActions}, timeoutMs=${timeoutMs})`
3855
3855
  );
3856
3856
  let stdout = "";
3857
3857
  try {
@@ -3882,26 +3882,26 @@ async function runComputerUseDriver(query2, options, mode, label) {
3882
3882
  telemetry3
3883
3883
  );
3884
3884
  }
3885
- diagnostic(`Computer Use ${label} failed: ${details}`);
3885
+ diagnostic(`Computer Use ${label2} failed: ${details}`);
3886
3886
  throw new ComputerUseDriverError(
3887
- `Computer Use ${label} failed: ${details}`,
3887
+ `Computer Use ${label2} failed: ${details}`,
3888
3888
  telemetry3
3889
3889
  );
3890
3890
  }
3891
3891
  diagnostic(
3892
- `Computer Use ${label} exited successfully (stdoutBytes=${stdout.length})`
3892
+ `Computer Use ${label2} exited successfully (stdoutBytes=${stdout.length})`
3893
3893
  );
3894
3894
  const record = parseLastJsonLine(stdout);
3895
3895
  const expectedStatus = mode === "submit" ? "submitted" : "hitl_checked";
3896
3896
  if (record?.status !== expectedStatus) {
3897
3897
  throw new ComputerUseDriverError(
3898
- `Computer Use ${label} did not reach ${expectedStatus}.`,
3898
+ `Computer Use ${label2} did not reach ${expectedStatus}.`,
3899
3899
  parseTelemetry(record?.telemetry, env, "partial")
3900
3900
  );
3901
3901
  }
3902
3902
  if (!isCount(record.action_count) || !isSafeModel(record.model, env)) {
3903
3903
  throw new ComputerUseDriverError(
3904
- `Computer Use ${label} returned invalid result fields.`,
3904
+ `Computer Use ${label2} returned invalid result fields.`,
3905
3905
  parseTelemetry(record.telemetry, env, "partial")
3906
3906
  );
3907
3907
  }
@@ -3912,7 +3912,7 @@ async function runComputerUseDriver(query2, options, mode, label) {
3912
3912
  ...telemetry2 ? { telemetry: telemetry2 } : {}
3913
3913
  };
3914
3914
  diagnostic(
3915
- `Computer Use ${label} completed (actions=${common.action_count})`
3915
+ `Computer Use ${label2} completed (actions=${common.action_count})`
3916
3916
  );
3917
3917
  if (mode === "hitl") return { status: "hitl_checked", ...common };
3918
3918
  const submission = asRecord(record.submission_action);
@@ -9053,10 +9053,57 @@ function escapeHtml(text) {
9053
9053
 
9054
9054
  // package.json
9055
9055
  var package_default = {
9056
- version: "2.0.0-beta.3"};
9056
+ version: "2.0.0-beta.4"};
9057
+
9058
+ // src/mcp/connectionDiagnostics.ts
9059
+ function classifyMCPConnectionFailure(error) {
9060
+ if (typeof error !== "object" || error === null) return "connection_failed";
9061
+ const details = error;
9062
+ const status = details.response?.status ?? details.status ?? details.code;
9063
+ if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) {
9064
+ return `http_${status}`;
9065
+ }
9066
+ const message = error instanceof Error ? error.message : "";
9067
+ const httpStatus = /\b(?:HTTP|status(?: code)?|code:)\s*[:=(]?\s*([45]\d{2})\b/i.exec(
9068
+ message
9069
+ )?.[1];
9070
+ if (httpStatus) return `http_${httpStatus}`;
9071
+ for (const code of ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND"]) {
9072
+ if (details.code === code || message.toUpperCase().includes(code)) {
9073
+ return code.toLowerCase();
9074
+ }
9075
+ }
9076
+ if (/timed out|timeout/i.test(message)) return "timeout";
9077
+ if (/network|socket hang up|fetch failed/i.test(message))
9078
+ return "network_error";
9079
+ return "connection_failed";
9080
+ }
9081
+ var MCPHttpConnectionError = class extends Error {
9082
+ constructor(streamableError, sseError, retryable, retryAfterMs) {
9083
+ const streamableHttpFailure = classifyMCPConnectionFailure(streamableError);
9084
+ const sseFailure = classifyMCPConnectionFailure(sseError);
9085
+ super(
9086
+ `MCP connection failed: streamableHttp=${streamableHttpFailure}; sse=${sseFailure}`
9087
+ );
9088
+ this.retryable = retryable;
9089
+ this.retryAfterMs = retryAfterMs;
9090
+ this.name = "MCPHttpConnectionError";
9091
+ this.streamableHttpFailure = streamableHttpFailure;
9092
+ this.sseFailure = sseFailure;
9093
+ }
9094
+ streamableHttpFailure;
9095
+ sseFailure;
9096
+ };
9097
+ function formatMCPConnectionFailure(error) {
9098
+ if (error instanceof MCPHttpConnectionError) {
9099
+ return `MCP connection failed: streamableHttp=${error.streamableHttpFailure}; sse=${error.sseFailure}`;
9100
+ }
9101
+ return classifyMCPConnectionFailure(error);
9102
+ }
9057
9103
 
9058
9104
  // src/mcp/clientFactory.ts
9059
9105
  function getRetryAfterDelayMs(err) {
9106
+ if (err instanceof MCPHttpConnectionError) return err.retryAfterMs;
9060
9107
  const response = err?.response;
9061
9108
  const retryAfter = response?.headers?.get?.("Retry-After");
9062
9109
  if (retryAfter) {
@@ -9075,6 +9122,7 @@ function isTransientNetworkError(err) {
9075
9122
  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");
9076
9123
  }
9077
9124
  function isRetryableError(err) {
9125
+ if (err instanceof MCPHttpConnectionError) return err.retryable;
9078
9126
  return isTransientNetworkError(err) || isRateLimitError(err);
9079
9127
  }
9080
9128
  async function retryWithBackoff(fn, maxAttempts) {
@@ -9092,7 +9140,7 @@ async function retryWithBackoff(fn, maxAttempts) {
9092
9140
  attempt + 1,
9093
9141
  maxAttempts + 1,
9094
9142
  delayMs,
9095
- err.message
9143
+ formatMCPConnectionFailure(err)
9096
9144
  );
9097
9145
  await new Promise((resolve8) => setTimeout(resolve8, delayMs));
9098
9146
  } else {
@@ -9249,15 +9297,24 @@ async function createMCPClientForConfig(config, options) {
9249
9297
  } catch (err) {
9250
9298
  debugHttp(
9251
9299
  "streamableHttp failed (%s), falling back to SSE",
9252
- err.message
9300
+ formatMCPConnectionFailure(err)
9253
9301
  );
9254
9302
  debugClient("Streamable HTTP failed, falling back to SSE transport");
9255
9303
  debugHttp("Attempting transport: sse");
9256
- const sseTransport = new SSEClientTransport(url, {
9257
- requestInit,
9258
- ...options?.authProvider ? { authProvider: options.authProvider } : {}
9259
- });
9260
- await client.connect(sseTransport, connectOptions);
9304
+ try {
9305
+ const sseTransport = new SSEClientTransport(url, {
9306
+ requestInit,
9307
+ ...options?.authProvider ? { authProvider: options.authProvider } : {}
9308
+ });
9309
+ await client.connect(sseTransport, connectOptions);
9310
+ } catch (sseError) {
9311
+ throw new MCPHttpConnectionError(
9312
+ err,
9313
+ sseError,
9314
+ isRetryableError(err) || isRetryableError(sseError),
9315
+ getRetryAfterDelayMs(err) ?? getRetryAfterDelayMs(sseError)
9316
+ );
9317
+ }
9261
9318
  debugClient("Connected via SSE");
9262
9319
  debugHttp("Connection established via sse");
9263
9320
  }
@@ -13715,6 +13772,86 @@ async function getCoworkPlatform(provider) {
13715
13772
  );
13716
13773
  }
13717
13774
 
13775
+ // src/evals/cowork/mcpReadiness.ts
13776
+ init_config();
13777
+ var PREFLIGHT_TIMEOUT_MS = 3e4;
13778
+ var CoworkMcpReadinessError = class extends Error {
13779
+ servers;
13780
+ constructor(servers) {
13781
+ super(
13782
+ `Cowork MCP preflight failed; no task was submitted. ${servers.map(
13783
+ (server) => `${server.label}=${server.status}${server.error ? `(${server.error})` : ""}(${server.elapsedMs}ms)`
13784
+ ).join(", ")}`
13785
+ );
13786
+ this.name = "CoworkMcpReadinessError";
13787
+ this.servers = servers;
13788
+ }
13789
+ };
13790
+ function label(server, index) {
13791
+ return server.label ?? `server-${index + 1}`;
13792
+ }
13793
+ async function withTimeout(promise, timeoutMs) {
13794
+ let timer;
13795
+ try {
13796
+ return await Promise.race([
13797
+ promise,
13798
+ new Promise((_, reject) => {
13799
+ timer = setTimeout(
13800
+ () => reject(new Error("MCP preflight timed out")),
13801
+ timeoutMs
13802
+ );
13803
+ })
13804
+ ]);
13805
+ } finally {
13806
+ if (timer) clearTimeout(timer);
13807
+ }
13808
+ }
13809
+ function resolveServer(server, env) {
13810
+ if (!isHttpConfig(server)) return server;
13811
+ const [coworkServer] = toCoworkServers([server]);
13812
+ if (!coworkServer) throw new Error("Invalid Cowork MCP configuration.");
13813
+ const headers = resolveCoworkMcpHeaders([coworkServer], env);
13814
+ return {
13815
+ ...server,
13816
+ // Use the same validated runtime headers as Cowork setup.
13817
+ auth: void 0,
13818
+ headers: headers[coworkServer.label]
13819
+ };
13820
+ }
13821
+ async function verifyCoworkMcpServers(servers, env) {
13822
+ const results = await Promise.all(
13823
+ servers.map(async (server, index) => {
13824
+ const started = Date.now();
13825
+ let client;
13826
+ try {
13827
+ client = await createMCPClientForConfig(resolveServer(server, env));
13828
+ const tools = await withTimeout(
13829
+ client.listTools(),
13830
+ PREFLIGHT_TIMEOUT_MS
13831
+ );
13832
+ return {
13833
+ label: label(server, index),
13834
+ status: "connected",
13835
+ toolCount: tools.tools.length,
13836
+ elapsedMs: Date.now() - started
13837
+ };
13838
+ } catch (error) {
13839
+ return {
13840
+ label: label(server, index),
13841
+ status: "failed",
13842
+ elapsedMs: Date.now() - started,
13843
+ error: formatMCPConnectionFailure(error)
13844
+ };
13845
+ } finally {
13846
+ if (client) await closeMCPClient(client).catch(() => void 0);
13847
+ }
13848
+ })
13849
+ );
13850
+ if (results.some((result) => result.status !== "connected"))
13851
+ throw new CoworkMcpReadinessError(results);
13852
+ return results;
13853
+ }
13854
+
13718
13855
  // src/evals/coworkHost.ts
13719
13856
  init_driver();
13720
13857
  var OptionsSchema = z.object({
@@ -13797,6 +13934,14 @@ async function runBatch(requests, context, selectedPlatform) {
13797
13934
  env,
13798
13935
  model: config.model
13799
13936
  });
13937
+ if (servers.length) {
13938
+ const readiness = await verifyCoworkMcpServers(servers, env);
13939
+ process.stderr.write(
13940
+ `[mst:cowork] MCP preflight ready: ${readiness.map(
13941
+ (server) => `${server.label}(${server.toolCount ?? 0} tools, ${server.elapsedMs}ms)`
13942
+ ).join(", ")}\\n`
13943
+ );
13944
+ }
13800
13945
  for (const [index, request] of requests.entries()) {
13801
13946
  const caseStartedAt = Date.now();
13802
13947
  const computerUse = {
@@ -14306,8 +14451,8 @@ function claudeCliHost(options) {
14306
14451
  if (options.servers) {
14307
14452
  for (const key of Object.keys(mcpServers)) delete mcpServers[key];
14308
14453
  for (const entry of options.servers) {
14309
- const label = entry.label ?? "mcp-server";
14310
- mcpServers[label] = entry.transport === "http" ? {
14454
+ const label2 = entry.label ?? "mcp-server";
14455
+ mcpServers[label2] = entry.transport === "http" ? {
14311
14456
  type: "http",
14312
14457
  url: entry.serverUrl,
14313
14458
  headers: {
@@ -16261,9 +16406,9 @@ function buildArmDeltas(arms) {
16261
16406
  );
16262
16407
  }
16263
16408
  function redactServerForReport(server) {
16264
- const label = server.label ? { label: server.label } : {};
16409
+ const label2 = server.label ? { label: server.label } : {};
16265
16410
  if (server.transport === "stdio") {
16266
- return { transport: "stdio", command: server.command, ...label };
16411
+ return { transport: "stdio", command: server.command, ...label2 };
16267
16412
  }
16268
16413
  const url = new URL(server.serverUrl);
16269
16414
  url.username = "";
@@ -16273,7 +16418,7 @@ function redactServerForReport(server) {
16273
16418
  return {
16274
16419
  transport: "http",
16275
16420
  serverUrl: url.toString(),
16276
- ...label,
16421
+ ...label2,
16277
16422
  ...server.auth?.accessTokenEnv ? { auth: { accessTokenEnv: server.auth.accessTokenEnv } } : {}
16278
16423
  };
16279
16424
  }