fix(mcp): await failed transport cleanup on Windows
Wait for failed MCP transport cleanup before command exit so targeted live checks do not crash on Windows. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
48
src/services/mcp/client.test.ts
Normal file
48
src/services/mcp/client.test.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
import { cleanupFailedConnection } from './client.js'
|
||||||
|
|
||||||
|
test('cleanupFailedConnection awaits transport close before resolving', async () => {
|
||||||
|
let closed = false
|
||||||
|
let resolveClose: (() => void) | undefined
|
||||||
|
|
||||||
|
const transport = {
|
||||||
|
close: async () =>
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
resolveClose = () => {
|
||||||
|
closed = true
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanupPromise = cleanupFailedConnection(transport)
|
||||||
|
|
||||||
|
assert.equal(closed, false)
|
||||||
|
resolveClose?.()
|
||||||
|
await cleanupPromise
|
||||||
|
assert.equal(closed, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cleanupFailedConnection closes in-process server and transport', async () => {
|
||||||
|
let inProcessClosed = false
|
||||||
|
let transportClosed = false
|
||||||
|
|
||||||
|
const inProcessServer = {
|
||||||
|
close: async () => {
|
||||||
|
inProcessClosed = true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const transport = {
|
||||||
|
close: async () => {
|
||||||
|
transportClosed = true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await cleanupFailedConnection(transport, inProcessServer)
|
||||||
|
|
||||||
|
assert.equal(inProcessClosed, true)
|
||||||
|
assert.equal(transportClosed, true)
|
||||||
|
})
|
||||||
@@ -560,6 +560,22 @@ function getRemoteMcpServerConnectionBatchSize(): number {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InProcessMcpServer = {
|
||||||
|
connect(t: Transport): Promise<void>
|
||||||
|
close(): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cleanupFailedConnection(
|
||||||
|
transport: Pick<Transport, 'close'>,
|
||||||
|
inProcessServer?: Pick<InProcessMcpServer, 'close'>,
|
||||||
|
): Promise<void> {
|
||||||
|
if (inProcessServer) {
|
||||||
|
await inProcessServer.close().catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
await transport.close().catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
function isLocalMcpServer(config: ScopedMcpServerConfig): boolean {
|
function isLocalMcpServer(config: ScopedMcpServerConfig): boolean {
|
||||||
return !config.type || config.type === 'stdio' || config.type === 'sdk'
|
return !config.type || config.type === 'stdio' || config.type === 'sdk'
|
||||||
}
|
}
|
||||||
@@ -606,9 +622,7 @@ export const connectToServer = memoize(
|
|||||||
},
|
},
|
||||||
): Promise<MCPServerConnection> => {
|
): Promise<MCPServerConnection> => {
|
||||||
const connectStartTime = Date.now()
|
const connectStartTime = Date.now()
|
||||||
let inProcessServer:
|
let inProcessServer: InProcessMcpServer | undefined
|
||||||
| { connect(t: Transport): Promise<void>; close(): Promise<void> }
|
|
||||||
| undefined
|
|
||||||
try {
|
try {
|
||||||
let transport
|
let transport
|
||||||
|
|
||||||
@@ -1145,9 +1159,10 @@ export const connectToServer = memoize(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (inProcessServer) {
|
if (inProcessServer) {
|
||||||
inProcessServer.close().catch(() => { })
|
await cleanupFailedConnection(transport, inProcessServer)
|
||||||
|
} else {
|
||||||
|
await cleanupFailedConnection(transport)
|
||||||
}
|
}
|
||||||
transport.close().catch(() => { })
|
|
||||||
if (stderrOutput) {
|
if (stderrOutput) {
|
||||||
logMCPError(name, `Server stderr: ${stderrOutput}`)
|
logMCPError(name, `Server stderr: ${stderrOutput}`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user