(url, init)
| 297 | staticOAuthBearer: boolean, |
| 298 | ): FetchLike => { |
| 299 | const execute: FetchLike = async (url, init) => { |
| 300 | const headers = headersFrom(init?.headers); |
| 301 | const requestWithoutBody = HttpClientRequest.make(httpMethodFrom(init?.method))(url, { |
| 302 | headers: recordFromHeaders(headers), |
| 303 | }); |
| 304 | const request = await applyBody(requestWithoutBody, headers, init?.body); |
| 305 | const effect = Effect.gen(function* () { |
| 306 | const client = yield* HttpClient.HttpClient; |
| 307 | const response = yield* client.execute(request); |
| 308 | const responseHeaders = new Headers(); |
| 309 | for (const [key, value] of Object.entries(response.headers)) { |
| 310 | if (value !== undefined) responseHeaders.set(key, value); |
| 311 | } |
| 312 | // Abort must reach the body, not just the pending request: this stream |
| 313 | // fiber outlives the `runPromise` below, so without this streamable |
| 314 | // http's SSE `GET` stays in flight after `close()`. Interrupted at the |
| 315 | // source because the SDK holds a locked reader on that same stream, |
| 316 | // which rules out cancelling the ReadableStream. |
| 317 | const stream = |
| 318 | init?.signal == null |
| 319 | ? response.stream |
| 320 | : Stream.interruptWhen(response.stream, awaitAbort(init.signal)); |
| 321 | const body = |
| 322 | response.status === 204 || response.status === 205 || response.status === 304 |
| 323 | ? null |
| 324 | : Stream.toReadableStream(stream); |
| 325 | return new Response(body, { |
| 326 | status: response.status, |
| 327 | headers: responseHeaders, |
| 328 | }); |
| 329 | }).pipe(Effect.mapError(normalizeHttpClientFailure), Effect.provide(httpClientLayer)); |
| 330 | // Executor resolves and refreshes OAuth credentials before constructing |
| 331 | // this transport. If that stored bearer is rejected, the MCP SDK cannot |
| 332 | // complete its interactive fallback in a catalog refresh and would perform |
| 333 | // avoidable DCR first. Stop at the authenticated HTTP boundary instead. |
| 334 | // A 403 carrying an RFC 6750 insufficient_scope challenge is intercepted |
| 335 | // HERE, below the SDK: with an authProvider the SDK would consume the |
| 336 | // challenge and re-run auth ("upscoping"), which our static-token |
| 337 | // provider can only answer by demanding reauthorization — misclassifying |
| 338 | // an unfixable scope shortfall as oauth_reauth_required. Thrown as the |
| 339 | // tagged error from the fetch adapter (a true runtime edge: the SDK |
| 340 | // consumes promise rejections) so it reaches the invoke/connect catch |
| 341 | // sites verbatim. |
| 342 | const promise = Effect.runPromise(effect).then(async (response) => { |
| 343 | let settled = response; |
| 344 | if (staticOAuthBearer && settled.status === 401) { |
| 345 | // One immediate replay before classifying — but only for requests the |
| 346 | // allowlist proves read-only (`isReplaySafeRequest`). A lone 401 on |
| 347 | // discovery/handshake traffic can be a transient upstream blip (a |
| 348 | // proxy hiccup, a racing key rotation on the server), and stamping |
| 349 | // reauthorization-required from a single sample forces a needless |
| 350 | // reconnect; the replay itself is possible because every body this |
| 351 | // adapter builds is buffered (`applyBody`), never a one-shot stream. |
| 352 | // A side-effectful method (`tools/call`) never replays: a 401 does |
| 353 | // not guarantee the server did no work first, so re-sending could |
| 354 | // execute the action twice. Its single 401 classifies directly — |
| 355 | // the invocation has already failed either way, and the |
| 356 | // transient-blip concern only justified the retry on read-only |
nothing calls this directly
no test coverage detected