Skip to content

Commit 9574930

Browse files
fix(http): add 30s connect timeout to prevent indefinite hangs
HttpMcpClient.connect() previously had no timeout, so unresponsive OAuth discovery endpoints or slow MCP servers could hang forever. - Add 30s default timeout (configurable via SUPER_MCP_CONNECT_TIMEOUT_MS) - Apply to primary connect, SSE fallback, and finishOAuth reconnect - Clean up timer on success, swallow late rejections on timeout Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 3724c4c commit 9574930

File tree

1 file changed

+29
-3
lines changed

1 file changed

+29
-3
lines changed

src/clients/httpClient.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export interface HttpMcpClientOptions {
2525
oauthProvider?: SimpleOAuthProvider;
2626
}
2727

28+
// Default timeout for connect() to prevent hanging on unresponsive OAuth
29+
// discovery endpoints or slow MCP servers. Covers the full transport
30+
// negotiation + OAuth token refresh cycle.
31+
const CONNECT_TIMEOUT_MS = 30_000;
32+
2833
export class HttpMcpClient implements McpClient {
2934
private client: Client;
3035
private transport?: SSEClientTransport | StreamableHTTPClientTransport;
@@ -136,7 +141,7 @@ export class HttpMcpClient implements McpClient {
136141
this.transport = this.createTransport();
137142

138143
try {
139-
await this.client.connect(this.transport);
144+
await this.connectWithTimeout(this.client, this.transport);
140145
this.isConnected = true;
141146

142147
logger.info("Successfully connected to MCP server", {
@@ -183,7 +188,7 @@ export class HttpMcpClient implements McpClient {
183188

184189
// Create SSE transport and connect
185190
this.transport = this.createTransport();
186-
await this.client.connect(this.transport);
191+
await this.connectWithTimeout(this.client, this.transport);
187192
this.isConnected = true;
188193

189194
logger.info("Successfully connected to MCP server using SSE fallback", {
@@ -245,6 +250,27 @@ export class HttpMcpClient implements McpClient {
245250
}
246251
}
247252

253+
private async connectWithTimeout(
254+
client: Client,
255+
transport: SSEClientTransport | StreamableHTTPClientTransport
256+
): Promise<void> {
257+
const timeoutMs = Number(process.env.SUPER_MCP_CONNECT_TIMEOUT_MS) || CONNECT_TIMEOUT_MS;
258+
let timer: ReturnType<typeof setTimeout>;
259+
const timeout = new Promise<never>((_, reject) => {
260+
timer = setTimeout(
261+
() => reject(new Error(`Connection timed out after ${timeoutMs}ms for package '${this.packageId}'`)),
262+
timeoutMs
263+
);
264+
});
265+
const connectPromise = client.connect(transport);
266+
try {
267+
await Promise.race([connectPromise, timeout]);
268+
} finally {
269+
clearTimeout(timer!);
270+
connectPromise.catch(() => {});
271+
}
272+
}
273+
248274
private createTransport(): SSEClientTransport | StreamableHTTPClientTransport {
249275
const url = new URL(this.config.base_url!);
250276
const options = this.getTransportOptions();
@@ -488,7 +514,7 @@ export class HttpMcpClient implements McpClient {
488514

489515
this.transport = this.createTransport();
490516

491-
await this.client.connect(this.transport);
517+
await this.connectWithTimeout(this.client, this.transport);
492518
this.isConnected = true;
493519
logger.info("Client connected successfully with OAuth tokens", { package_id: this.packageId });
494520
} catch (error) {

0 commit comments

Comments
 (0)