@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.cjs CHANGED
@@ -3867,22 +3867,22 @@ async function runAnthropicComputerUseHitl(options) {
3867
3867
  "HITL check"
3868
3868
  );
3869
3869
  }
3870
- async function runComputerUseDriver(query2, options, mode, label) {
3870
+ async function runComputerUseDriver(query2, options, mode, label2) {
3871
3871
  const env = {
3872
3872
  ...process.env,
3873
3873
  ...options.env,
3874
3874
  ...options.model ? { MST_COWORK_CUA_MODEL: options.model } : {}
3875
3875
  };
3876
3876
  if (options.deadlineAt <= Date.now())
3877
- throw new Error(`Computer Use ${label} deadline exceeded; not retrying.`);
3877
+ throw new Error(`Computer Use ${label2} deadline exceeded; not retrying.`);
3878
3878
  const python = await ensureCoworkPython(env);
3879
3879
  const DRIVER_PATH = resolveDriverPath(env);
3880
3880
  const timeoutMs = options.deadlineAt - Date.now();
3881
3881
  if (timeoutMs <= 0)
3882
- throw new Error(`Computer Use ${label} deadline exceeded; not retrying.`);
3882
+ throw new Error(`Computer Use ${label2} deadline exceeded; not retrying.`);
3883
3883
  const maxActions = options.maxActions ?? (mode === "hitl" ? 12 : 24);
3884
3884
  diagnostic(
3885
- `starting Computer Use ${label} (script=${DRIVER_PATH}, maxActions=${maxActions}, timeoutMs=${timeoutMs})`
3885
+ `starting Computer Use ${label2} (script=${DRIVER_PATH}, maxActions=${maxActions}, timeoutMs=${timeoutMs})`
3886
3886
  );
3887
3887
  let stdout = "";
3888
3888
  try {
@@ -3913,26 +3913,26 @@ async function runComputerUseDriver(query2, options, mode, label) {
3913
3913
  telemetry3
3914
3914
  );
3915
3915
  }
3916
- diagnostic(`Computer Use ${label} failed: ${details}`);
3916
+ diagnostic(`Computer Use ${label2} failed: ${details}`);
3917
3917
  throw new ComputerUseDriverError(
3918
- `Computer Use ${label} failed: ${details}`,
3918
+ `Computer Use ${label2} failed: ${details}`,
3919
3919
  telemetry3
3920
3920
  );
3921
3921
  }
3922
3922
  diagnostic(
3923
- `Computer Use ${label} exited successfully (stdoutBytes=${stdout.length})`
3923
+ `Computer Use ${label2} exited successfully (stdoutBytes=${stdout.length})`
3924
3924
  );
3925
3925
  const record = parseLastJsonLine(stdout);
3926
3926
  const expectedStatus = mode === "submit" ? "submitted" : "hitl_checked";
3927
3927
  if (record?.status !== expectedStatus) {
3928
3928
  throw new ComputerUseDriverError(
3929
- `Computer Use ${label} did not reach ${expectedStatus}.`,
3929
+ `Computer Use ${label2} did not reach ${expectedStatus}.`,
3930
3930
  parseTelemetry(record?.telemetry, env, "partial")
3931
3931
  );
3932
3932
  }
3933
3933
  if (!isCount(record.action_count) || !isSafeModel(record.model, env)) {
3934
3934
  throw new ComputerUseDriverError(
3935
- `Computer Use ${label} returned invalid result fields.`,
3935
+ `Computer Use ${label2} returned invalid result fields.`,
3936
3936
  parseTelemetry(record.telemetry, env, "partial")
3937
3937
  );
3938
3938
  }
@@ -3943,7 +3943,7 @@ async function runComputerUseDriver(query2, options, mode, label) {
3943
3943
  ...telemetry2 ? { telemetry: telemetry2 } : {}
3944
3944
  };
3945
3945
  diagnostic(
3946
- `Computer Use ${label} completed (actions=${common.action_count})`
3946
+ `Computer Use ${label2} completed (actions=${common.action_count})`
3947
3947
  );
3948
3948
  if (mode === "hitl") return { status: "hitl_checked", ...common };
3949
3949
  const submission = asRecord(record.submission_action);
@@ -9084,10 +9084,57 @@ function escapeHtml(text) {
9084
9084
 
9085
9085
  // package.json
9086
9086
  var package_default = {
9087
- version: "2.0.0-beta.3"};
9087
+ version: "2.0.0-beta.4"};
9088
+
9089
+ // src/mcp/connectionDiagnostics.ts
9090
+ function classifyMCPConnectionFailure(error) {
9091
+ if (typeof error !== "object" || error === null) return "connection_failed";
9092
+ const details = error;
9093
+ const status = details.response?.status ?? details.status ?? details.code;
9094
+ if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599) {
9095
+ return `http_${status}`;
9096
+ }
9097
+ const message = error instanceof Error ? error.message : "";
9098
+ const httpStatus = /\b(?:HTTP|status(?: code)?|code:)\s*[:=(]?\s*([45]\d{2})\b/i.exec(
9099
+ message
9100
+ )?.[1];
9101
+ if (httpStatus) return `http_${httpStatus}`;
9102
+ for (const code of ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND"]) {
9103
+ if (details.code === code || message.toUpperCase().includes(code)) {
9104
+ return code.toLowerCase();
9105
+ }
9106
+ }
9107
+ if (/timed out|timeout/i.test(message)) return "timeout";
9108
+ if (/network|socket hang up|fetch failed/i.test(message))
9109
+ return "network_error";
9110
+ return "connection_failed";
9111
+ }
9112
+ var MCPHttpConnectionError = class extends Error {
9113
+ constructor(streamableError, sseError, retryable, retryAfterMs) {
9114
+ const streamableHttpFailure = classifyMCPConnectionFailure(streamableError);
9115
+ const sseFailure = classifyMCPConnectionFailure(sseError);
9116
+ super(
9117
+ `MCP connection failed: streamableHttp=${streamableHttpFailure}; sse=${sseFailure}`
9118
+ );
9119
+ this.retryable = retryable;
9120
+ this.retryAfterMs = retryAfterMs;
9121
+ this.name = "MCPHttpConnectionError";
9122
+ this.streamableHttpFailure = streamableHttpFailure;
9123
+ this.sseFailure = sseFailure;
9124
+ }
9125
+ streamableHttpFailure;
9126
+ sseFailure;
9127
+ };
9128
+ function formatMCPConnectionFailure(error) {
9129
+ if (error instanceof MCPHttpConnectionError) {
9130
+ return `MCP connection failed: streamableHttp=${error.streamableHttpFailure}; sse=${error.sseFailure}`;
9131
+ }
9132
+ return classifyMCPConnectionFailure(error);
9133
+ }
9088
9134
 
9089
9135
  // src/mcp/clientFactory.ts
9090
9136
  function getRetryAfterDelayMs(err) {
9137
+ if (err instanceof MCPHttpConnectionError) return err.retryAfterMs;
9091
9138
  const response = err?.response;
9092
9139
  const retryAfter = response?.headers?.get?.("Retry-After");
9093
9140
  if (retryAfter) {
@@ -9106,6 +9153,7 @@ function isTransientNetworkError(err) {
9106
9153
  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");
9107
9154
  }
9108
9155
  function isRetryableError(err) {
9156
+ if (err instanceof MCPHttpConnectionError) return err.retryable;
9109
9157
  return isTransientNetworkError(err) || isRateLimitError(err);
9110
9158
  }
9111
9159
  async function retryWithBackoff(fn, maxAttempts) {
@@ -9123,7 +9171,7 @@ async function retryWithBackoff(fn, maxAttempts) {
9123
9171
  attempt + 1,
9124
9172
  maxAttempts + 1,
9125
9173
  delayMs,
9126
- err.message
9174
+ formatMCPConnectionFailure(err)
9127
9175
  );
9128
9176
  await new Promise((resolve8) => setTimeout(resolve8, delayMs));
9129
9177
  } else {
@@ -9280,15 +9328,24 @@ async function createMCPClientForConfig(config, options) {
9280
9328
  } catch (err) {
9281
9329
  debugHttp(
9282
9330
  "streamableHttp failed (%s), falling back to SSE",
9283
- err.message
9331
+ formatMCPConnectionFailure(err)
9284
9332
  );
9285
9333
  debugClient("Streamable HTTP failed, falling back to SSE transport");
9286
9334
  debugHttp("Attempting transport: sse");
9287
- const sseTransport = new sse_js.SSEClientTransport(url, {
9288
- requestInit,
9289
- ...options?.authProvider ? { authProvider: options.authProvider } : {}
9290
- });
9291
- await client.connect(sseTransport, connectOptions);
9335
+ try {
9336
+ const sseTransport = new sse_js.SSEClientTransport(url, {
9337
+ requestInit,
9338
+ ...options?.authProvider ? { authProvider: options.authProvider } : {}
9339
+ });
9340
+ await client.connect(sseTransport, connectOptions);
9341
+ } catch (sseError) {
9342
+ throw new MCPHttpConnectionError(
9343
+ err,
9344
+ sseError,
9345
+ isRetryableError(err) || isRetryableError(sseError),
9346
+ getRetryAfterDelayMs(err) ?? getRetryAfterDelayMs(sseError)
9347
+ );
9348
+ }
9292
9349
  debugClient("Connected via SSE");
9293
9350
  debugHttp("Connection established via sse");
9294
9351
  }
@@ -13746,6 +13803,86 @@ async function getCoworkPlatform(provider) {
13746
13803
  );
13747
13804
  }
13748
13805
 
13806
+ // src/evals/cowork/mcpReadiness.ts
13807
+ init_config();
13808
+ var PREFLIGHT_TIMEOUT_MS = 3e4;
13809
+ var CoworkMcpReadinessError = class extends Error {
13810
+ servers;
13811
+ constructor(servers) {
13812
+ super(
13813
+ `Cowork MCP preflight failed; no task was submitted. ${servers.map(
13814
+ (server) => `${server.label}=${server.status}${server.error ? `(${server.error})` : ""}(${server.elapsedMs}ms)`
13815
+ ).join(", ")}`
13816
+ );
13817
+ this.name = "CoworkMcpReadinessError";
13818
+ this.servers = servers;
13819
+ }
13820
+ };
13821
+ function label(server, index) {
13822
+ return server.label ?? `server-${index + 1}`;
13823
+ }
13824
+ async function withTimeout(promise, timeoutMs) {
13825
+ let timer;
13826
+ try {
13827
+ return await Promise.race([
13828
+ promise,
13829
+ new Promise((_, reject) => {
13830
+ timer = setTimeout(
13831
+ () => reject(new Error("MCP preflight timed out")),
13832
+ timeoutMs
13833
+ );
13834
+ })
13835
+ ]);
13836
+ } finally {
13837
+ if (timer) clearTimeout(timer);
13838
+ }
13839
+ }
13840
+ function resolveServer(server, env) {
13841
+ if (!isHttpConfig(server)) return server;
13842
+ const [coworkServer] = toCoworkServers([server]);
13843
+ if (!coworkServer) throw new Error("Invalid Cowork MCP configuration.");
13844
+ const headers = resolveCoworkMcpHeaders([coworkServer], env);
13845
+ return {
13846
+ ...server,
13847
+ // Use the same validated runtime headers as Cowork setup.
13848
+ auth: void 0,
13849
+ headers: headers[coworkServer.label]
13850
+ };
13851
+ }
13852
+ async function verifyCoworkMcpServers(servers, env) {
13853
+ const results = await Promise.all(
13854
+ servers.map(async (server, index) => {
13855
+ const started = Date.now();
13856
+ let client;
13857
+ try {
13858
+ client = await createMCPClientForConfig(resolveServer(server, env));
13859
+ const tools = await withTimeout(
13860
+ client.listTools(),
13861
+ PREFLIGHT_TIMEOUT_MS
13862
+ );
13863
+ return {
13864
+ label: label(server, index),
13865
+ status: "connected",
13866
+ toolCount: tools.tools.length,
13867
+ elapsedMs: Date.now() - started
13868
+ };
13869
+ } catch (error) {
13870
+ return {
13871
+ label: label(server, index),
13872
+ status: "failed",
13873
+ elapsedMs: Date.now() - started,
13874
+ error: formatMCPConnectionFailure(error)
13875
+ };
13876
+ } finally {
13877
+ if (client) await closeMCPClient(client).catch(() => void 0);
13878
+ }
13879
+ })
13880
+ );
13881
+ if (results.some((result) => result.status !== "connected"))
13882
+ throw new CoworkMcpReadinessError(results);
13883
+ return results;
13884
+ }
13885
+
13749
13886
  // src/evals/coworkHost.ts
13750
13887
  init_driver();
13751
13888
  var OptionsSchema = zod.z.object({
@@ -13828,6 +13965,14 @@ async function runBatch(requests, context, selectedPlatform) {
13828
13965
  env,
13829
13966
  model: config.model
13830
13967
  });
13968
+ if (servers.length) {
13969
+ const readiness = await verifyCoworkMcpServers(servers, env);
13970
+ process.stderr.write(
13971
+ `[mst:cowork] MCP preflight ready: ${readiness.map(
13972
+ (server) => `${server.label}(${server.toolCount ?? 0} tools, ${server.elapsedMs}ms)`
13973
+ ).join(", ")}\\n`
13974
+ );
13975
+ }
13831
13976
  for (const [index, request] of requests.entries()) {
13832
13977
  const caseStartedAt = Date.now();
13833
13978
  const computerUse = {
@@ -14337,8 +14482,8 @@ function claudeCliHost(options) {
14337
14482
  if (options.servers) {
14338
14483
  for (const key of Object.keys(mcpServers)) delete mcpServers[key];
14339
14484
  for (const entry of options.servers) {
14340
- const label = entry.label ?? "mcp-server";
14341
- mcpServers[label] = entry.transport === "http" ? {
14485
+ const label2 = entry.label ?? "mcp-server";
14486
+ mcpServers[label2] = entry.transport === "http" ? {
14342
14487
  type: "http",
14343
14488
  url: entry.serverUrl,
14344
14489
  headers: {
@@ -16292,9 +16437,9 @@ function buildArmDeltas(arms) {
16292
16437
  );
16293
16438
  }
16294
16439
  function redactServerForReport(server) {
16295
- const label = server.label ? { label: server.label } : {};
16440
+ const label2 = server.label ? { label: server.label } : {};
16296
16441
  if (server.transport === "stdio") {
16297
- return { transport: "stdio", command: server.command, ...label };
16442
+ return { transport: "stdio", command: server.command, ...label2 };
16298
16443
  }
16299
16444
  const url = new URL(server.serverUrl);
16300
16445
  url.username = "";
@@ -16304,7 +16449,7 @@ function redactServerForReport(server) {
16304
16449
  return {
16305
16450
  transport: "http",
16306
16451
  serverUrl: url.toString(),
16307
- ...label,
16452
+ ...label2,
16308
16453
  ...server.auth?.accessTokenEnv ? { auth: { accessTokenEnv: server.auth.accessTokenEnv } } : {}
16309
16454
  };
16310
16455
  }