different-ai/openwork · warning · EnterpriseMcpClientError
MCP_SHUTDOWN_FAILED
MCP_SHUTDOWN_FAILED
Error message
Enterprise MCP failed during ${phaseLabel[input.operationPhase]}${request}. What it means
runConnectedOperation in packages/enterprise-mcp-client/src/enterprise-mcp-client.ts throws an EnterpriseMcpClientError with operationPhase "shutdown" and code MCP_SHUTDOWN_FAILED when closing the MCP session (session.client.close()) fails or exceeds closeTimeoutMs AND the operation itself did not already fail. The work may have succeeded; only teardown failed. Note the guard: if operationFailed is true, the shutdown error is swallowed so the original operation error propagates instead.
Source
Thrown at packages/enterprise-mcp-client/src/enterprise-mcp-client.ts:426
operation: async (session) => {
try {
await connectWithProtocolNegotiation({
session,
connectionId: input.connection.id,
operationPhase: input.operationPhase,
})
let operationFailed = false
try {
return await input.operation(session)
} catch (error) {
operationFailed = true
throw error
} finally {
try {
await closeWithinDeadline(() => session.client.close(), closeTimeoutMs)
} catch (error) {
if (!operationFailed) {
throw new EnterpriseMcpClientError({
operationPhase: "shutdown",
requestPhase: session.observer.lastRequestPhase(),
cause: error,
})
}
}
}
} catch (error) {
await invalidateTerminallyRejectedCredential(session, {
connectionId: input.connection.id,
operationPhase: input.operationPhase,
})
throw error
}
},
})
}
View on GitHub (pinned to 2b7df46e8a)
Solutions
- Increase closeTimeoutMs in createEnterpriseMcpClient options if closes are merely slow.
- Treat this as teardown-only: check whether the operation result was already obtained; retry the operation if its result is unknown.
- Inspect error.cause to distinguish a timeout ("did not close before its deadline") from a transport close rejection.
- Check server-side connection handling/timeouts; a server that kills sockets during close triggers this.
Example fix
// before
createEnterpriseMcpClient({ closeTimeoutMs: 500 })
// after
createEnterpriseMcpClient({ closeTimeoutMs: 10_000 }) Defensive patterns
Strategy: try-catch
Try / catch
try {
result = await client.listTools(input)
} catch (error) {
if (error instanceof EnterpriseMcpClientError && error.operationPhase === "shutdown") {
// operation result unknown/lost to teardown: log cause, optionally retry
console.warn("MCP session close failed:", error.cause)
} else throw error
} Prevention
- Set a realistic closeTimeoutMs (default 5s) for slow or remote servers
- Monitor for repeated shutdown errors — they usually indicate server-side connection handling problems
- Don't assume the operation failed: shutdown errors occur only when the operation itself did not throw
When it happens
Trigger: listTools, callTool, listResources, readResource, or listResourceTemplates completes, but the finally-block close times out past closeTimeoutMs (default 5000ms) or the underlying transport's close() rejects (socket already destroyed, server hung up mid-close).
Common situations: Slow or half-dead server connections where the HTTP transport close hangs; very small configured closeTimeoutMs; network interruption right after a successful call; server that closes abruptly after responding.
Related errors
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/33057fa42afb8dcb.
Report an issue: GitHub.