From 980a6fe84e6fd2c2386ae365096e6753ec4fb50f Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 21 Jul 2026 14:11:35 -0700 Subject: [PATCH 1/2] diag(mcp): per-request phase logging on the streamable-HTTP transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the OAuth guarded-fetch logging that isolated the /oauth/start body stall. Logs each transport request (host+method), the response headers with TTFB, and failures — so a first-connect stall shows whether it hangs before response headers (connect/request) or after (the SDK's stream read), which isolates the client-side first-connect stall we haven't yet root-caused. --- apps/sim/lib/mcp/pinned-fetch.ts | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/mcp/pinned-fetch.ts b/apps/sim/lib/mcp/pinned-fetch.ts index 4de133fe81b..87942dca075 100644 --- a/apps/sim/lib/mcp/pinned-fetch.ts +++ b/apps/sim/lib/mcp/pinned-fetch.ts @@ -6,6 +6,7 @@ import { validateMcpServerSsrf } from '@/lib/mcp/domain-check' import { McpError } from '@/lib/mcp/types' const logger = createLogger('McpOauthFetch') +const transportLogger = createLogger('McpTransportFetch') /** Pinned fetch for the live MCP transport, plus a handle to release its sockets. */ export interface PinnedMcpFetch { @@ -25,7 +26,42 @@ export interface PinnedMcpFetch { */ export function createPinnedMcpFetch(resolvedIP: string): PinnedMcpFetch { const { fetch: pinnedFetch, dispatcher } = createPinnedFetchWithDispatcher(resolvedIP) - return { fetch: pinnedFetch, close: () => dispatcher.destroy() } + // Per-request phase logging: a stalled transport request (e.g. a first `initialize` that hangs + // to the client timeout) shows whether it stalls BEFORE response headers ("request" with no + // "response headers" = connect/request stall) or AFTER ("response headers" then the SDK's + // stream read stalls). Isolates the client-side first-connect stall. + const instrumentedFetch: typeof fetch = async (input, init) => { + const method = init?.method ?? (input instanceof Request ? input.method : 'GET') + const target = + typeof input === 'string' + ? input + : input instanceof URL + ? input.href + : input instanceof Request + ? input.url + : String(input) + const host = URL.canParse(target) ? new URL(target).host : target + const startedAt = Date.now() + transportLogger.info('MCP transport request', { host, method }) + try { + const response = await pinnedFetch(input, init) + transportLogger.info('MCP transport response headers', { + host, + method, + status: response.status, + ttfbMs: Date.now() - startedAt, + }) + return response + } catch (error) { + transportLogger.warn('MCP transport request failed', { + host, + method, + ms: Date.now() - startedAt, + }) + throw error + } + } + return { fetch: instrumentedFetch, close: () => dispatcher.destroy() } } /** From a12bac543161ed542e8b92d36bc4a401e807d753 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 21 Jul 2026 14:48:59 -0700 Subject: [PATCH 2/2] diag(mcp): include error name/code in transport failure log Distinguishes abort vs connect-refused vs TLS vs timeout in the failure-phase data. --- apps/sim/lib/mcp/pinned-fetch.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/sim/lib/mcp/pinned-fetch.ts b/apps/sim/lib/mcp/pinned-fetch.ts index 87942dca075..162f568c2af 100644 --- a/apps/sim/lib/mcp/pinned-fetch.ts +++ b/apps/sim/lib/mcp/pinned-fetch.ts @@ -53,10 +53,14 @@ export function createPinnedMcpFetch(resolvedIP: string): PinnedMcpFetch { }) return response } catch (error) { + const e = error as { name?: string; code?: string; cause?: { name?: string; code?: string } } transportLogger.warn('MCP transport request failed', { host, method, ms: Date.now() - startedAt, + errorName: e?.name, + errorCode: e?.code ?? e?.cause?.code, + causeName: e?.cause?.name, }) throw error }