Skip to content

Commit f7bb652

Browse files
committed
fix(mcp): abort hanging connections to non-standard servers
Add AbortController support to remote MCP transports (StreamableHTTP and SSE) to prevent indefinite hangs when servers send malformed responses or stall during handshake. - Pass AbortSignal to transport requestInit for native fetch abort - Set timeout to abort connections after configured timeout - Ensure cleanup on all error paths (timeout, error, success) Fixes #8406
1 parent f9fcdea commit f7bb652

File tree

1 file changed

+25
-4
lines changed

1 file changed

+25
-4
lines changed

packages/opencode/src/mcp/index.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,38 +318,59 @@ export namespace MCP {
318318
)
319319
}
320320

321-
const transports: Array<{ name: string; transport: TransportWithAuth }> = [
321+
const streamableAbort = new AbortController()
322+
const sseAbort = new AbortController()
323+
324+
const transports: Array<{ name: string; transport: TransportWithAuth; abort: AbortController }> = [
322325
{
323326
name: "StreamableHTTP",
327+
abort: streamableAbort,
324328
transport: new StreamableHTTPClientTransport(new URL(mcp.url), {
325329
authProvider,
326-
requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
330+
requestInit: mcp.headers
331+
? { headers: mcp.headers, signal: streamableAbort.signal }
332+
: { signal: streamableAbort.signal },
327333
}),
328334
},
329335
{
330336
name: "SSE",
337+
abort: sseAbort,
331338
transport: new SSEClientTransport(new URL(mcp.url), {
332339
authProvider,
333-
requestInit: mcp.headers ? { headers: mcp.headers } : undefined,
340+
requestInit: mcp.headers
341+
? { headers: mcp.headers, signal: sseAbort.signal }
342+
: { signal: sseAbort.signal },
334343
}),
335344
},
336345
]
337346

338347
let lastError: Error | undefined
339348
const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
340-
for (const { name, transport } of transports) {
349+
for (const { name, transport, abort } of transports) {
341350
try {
342351
const client = new Client({
343352
name: "opencode",
344353
version: Installation.VERSION,
345354
})
355+
const timer = setTimeout(() => {
356+
log.debug("connection timeout, aborting transport", { key, transport: name })
357+
abort.abort()
358+
}, connectTimeout)
359+
346360
await withTimeout(client.connect(transport), connectTimeout)
361+
.then(() => clearTimeout(timer))
362+
.catch((error) => {
363+
clearTimeout(timer)
364+
abort.abort()
365+
throw error
366+
})
347367
registerNotificationHandlers(client, key)
348368
mcpClient = client
349369
log.info("connected", { key, transport: name })
350370
status = { status: "connected" }
351371
break
352372
} catch (error) {
373+
abort.abort()
353374
lastError = error instanceof Error ? error : new Error(String(error))
354375

355376
// Handle OAuth-specific errors

0 commit comments

Comments
 (0)