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

  1. Increase closeTimeoutMs in createEnterpriseMcpClient options if closes are merely slow.
  2. Treat this as teardown-only: check whether the operation result was already obtained; retry the operation if its result is unknown.
  3. Inspect error.cause to distinguish a timeout ("did not close before its deadline") from a transport close rejection.
  4. 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

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.