different-ai/openwork · error · ProbeFailure
Connection closed before the tool response completed
Error message
Connection closed before the tool response completed
What it means
The tools/call POST is wrapped so that any transport failure before a response arrives is converted into a deterministic probe failure. For mutation tools the probe reports PROVIDER_EXECUTION / mutation_indeterminate (the commit may or may not have happened); for everything else it reports MCP_TOOL_EXECUTION / mcp_tool with this message.
Source
Thrown at packages/enterprise-mcp-mock-server/src/testing/probe.ts:886
const mutationRequired = fault?.effect === "commit-then-disconnect"
const tool = discoveredTools.find((candidate) => candidate.kind === (mutationRequired ? "mutation" : "read"))
if (!tool) throw new ProbeFailure("MCP_TOOL_EXECUTION", "mcp_tool", "No suitable tool was available for the requested probe")
return { name: tool.name, arguments: defaultArguments(tool.inputSchema) }
})()
const selectedTool = profile.tools.find((tool) => tool.name === selected.name)
if (mode === "safe-read" && selectedTool?.kind !== "read") {
throw new ProbeFailure("CONFIGURATION", "configuration", "safe-read mode accepts only a declared read-only tool")
}
startedAt = Date.now()
let toolResponse: Response
try {
toolResponse = await fetchStep(mcpUrl, {
method: "POST",
headers: sessionHeaders,
body: JSON.stringify({ jsonrpc: "2.0", id: 100, method: "tools/call", params: selected }),
}, "MCP_TOOL_EXECUTION", overallDeadline)
} catch {
throw new ProbeFailure(
selectedTool?.kind === "mutation" ? "PROVIDER_EXECUTION" : "MCP_TOOL_EXECUTION",
selectedTool?.kind === "mutation" ? "mutation_indeterminate" : "mcp_tool",
"Connection closed before the tool response completed",
)
}
const envelope = await parseRpc(await expectOk(toolResponse, "MCP_TOOL_EXECUTION"), "MCP_TOOL_EXECUTION")
if (envelope.id !== 100) {
throw new ProbeFailure("MCP_TOOL_EXECUTION", "mcp_tool", "tools/call response JSON-RPC id did not match the request")
}
if (envelope.error) throw new ProbeFailure("MCP_TOOL_EXECUTION", "mcp_tool", envelope.error.message)
const toolResult = parseAt(
z.object({ isError: z.boolean(), structuredContent: z.unknown().optional() }),
envelope.result,
"MCP_TOOL_EXECUTION",
"mcp_tool",
"Tool response did not match the MCP result shape",
)
if (toolResult.isError) {View on GitHub (pinned to 2b7df46e8a)
Solutions
- If testing the commit-then-disconnect fault, expect mutation_indeterminate and assert the downstream commit/idempotency behavior instead of treating it as a bug.
- Verify the mock server is running and mcpUrl/sessionHeaders are correct and current.
- Increase overallDeadline if the tool legitimately takes longer than the budget.
- Make the mutation tool idempotent so a retry after an indeterminate failure is safe.
Defensive patterns
Strategy: try-catch
Validate before calling
// before calling the tool, confirm the endpoint is reachable:
const ping = await fetch(mcpUrl, { method: "POST", headers: sessionHeaders, body: JSON.stringify({ jsonrpc: "2.0", id: 0, method: "ping" }) });
if (!ping.ok) throw new Error(`MCP endpoint unreachable: ${ping.status}`); Try / catch
try {
await probeEnterpriseMcpMockServer(options);
} catch (e) {
if (e instanceof ProbeFailure && e.reason === "mutation_indeterminate") {
// check downstream side effects before deciding pass/fail; do not auto-retry mutations
} else if (e instanceof ProbeFailure && e.reason === "mcp_tool" && e.message.includes("Connection closed")) {
// verify server health and deadline, then retry once for read-only calls
} else throw e;
} Prevention
- Budget overallDeadline generously relative to the slowest tool.
- Keep the mock server alive for the entire probe session.
- Make mutation tools idempotent so indeterminate outcomes are recoverable.
- Reuse session headers exactly as issued during initialize.
When it happens
Trigger: The fetch of POST tools/call (id 100) with sessionHeaders throws or times out against the overallDeadline — connection reset, server crashed mid-call (e.g. commit-then-disconnect fault), or deadline exceeded before headers arrive.
Common situations: Mock server deliberately disconnects after committing (commit-then-disconnect fault scenario); server not listening on mcpUrl; network proxy dropping keep-alive connections; overallDeadline too tight for a slow tool.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- Request timed out.
- Timed out waiting for server health
- Authorization for ${input.connectionName} did not finish. Co
- Connection lost
- the hostname does not resolve
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/818339056bc93a45.
Report an issue: GitHub.