@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 +172 -25
- package/dist/fixtures/mcp.js +65 -8
- package/dist/fixtures/mcp.js.map +1 -1
- package/dist/index.cjs +168 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +168 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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,
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
3657
|
+
diagnostic(`Computer Use ${label2} failed: ${details}`);
|
|
3658
3658
|
throw new ComputerUseDriverError(
|
|
3659
|
-
`Computer Use ${
|
|
3659
|
+
`Computer Use ${label2} failed: ${details}`,
|
|
3660
3660
|
telemetry3
|
|
3661
3661
|
);
|
|
3662
3662
|
}
|
|
3663
3663
|
diagnostic(
|
|
3664
|
-
`Computer Use ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
6608
|
+
formatMCPConnectionFailure(err)
|
|
6560
6609
|
);
|
|
6561
6610
|
debugClient("Streamable HTTP failed, falling back to SSE transport");
|
|
6562
6611
|
debugHttp("Attempting transport: sse");
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
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
|
|
12892
|
-
mcpServers[
|
|
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
|
|
16470
|
+
const label2 = server.label ? { label: server.label } : {};
|
|
16324
16471
|
if (server.transport === "stdio") {
|
|
16325
|
-
return { transport: "stdio", command: server.command, ...
|
|
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
|
-
...
|
|
16482
|
+
...label2,
|
|
16336
16483
|
...server.auth?.accessTokenEnv ? { auth: { accessTokenEnv: server.auth.accessTokenEnv } } : {}
|
|
16337
16484
|
};
|
|
16338
16485
|
}
|
package/dist/fixtures/mcp.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
2283
|
+
formatMCPConnectionFailure(err)
|
|
2236
2284
|
);
|
|
2237
2285
|
debugClient("Streamable HTTP failed, falling back to SSE transport");
|
|
2238
2286
|
debugHttp("Attempting transport: sse");
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
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
|
}
|