diff --git a/packages/testing/playwright/services/mcp-api-helper.ts b/packages/testing/playwright/services/mcp-api-helper.ts index 8429f7fc9b1..ebf6982719f 100644 --- a/packages/testing/playwright/services/mcp-api-helper.ts +++ b/packages/testing/playwright/services/mcp-api-helper.ts @@ -2,12 +2,15 @@ import type { APIResponse } from '@playwright/test'; import * as http from 'http'; import * as https from 'https'; import { nanoid } from 'nanoid'; +import { setTimeout as wait } from 'node:timers/promises'; import type { ApiHelpers } from './api-helper'; import { N8N_AUTH_COOKIE } from '../config/constants'; type HttpMethod = 'GET' | 'POST' | 'DELETE'; +class SseNotFoundError extends Error {} + interface SseConnection { sessionId: string; postUrl: string; @@ -176,6 +179,29 @@ export class McpApiHelper { * @returns McpSession with sessionId and postUrl for sending messages */ async sseSetup( + path: string, + options?: { + headers?: Record; + maxNotFoundRetries?: number; + notFoundRetryDelayMs?: number; + }, + ): Promise { + const maxNotFoundRetries = options?.maxNotFoundRetries ?? 5; + const notFoundRetryDelayMs = options?.notFoundRetryDelayMs ?? 500; + + for (let attempt = 0; attempt <= maxNotFoundRetries; attempt++) { + try { + return await this.attemptSseSetup(path, options); + } catch (error) { + if (!(error instanceof SseNotFoundError) || attempt === maxNotFoundRetries) throw error; + await wait(notFoundRetryDelayMs); + } + } + // unreachable: the catch above rethrows on the final attempt. Here to satisfy TS. + throw new Error('SSE setup: retry loop exhausted'); + } + + private async attemptSseSetup( path: string, options?: { headers?: Record }, ): Promise { @@ -216,6 +242,14 @@ export class McpApiHelper { headers, }, (res) => { + if (res.statusCode === 404) { + // Drain the response so the socket is released back to the pool across retries. + res.resume(); + clearTimeout(timeout); + reject(new SseNotFoundError(`SSE setup got 404 for ${path}`)); + return; + } + let buffer = ''; let resolved = false;