(url, init)
| 232 | httpClientLayer: Layer.Layer<HttpClient.HttpClient>, |
| 233 | ): FetchLike => { |
| 234 | const execute: FetchLike = async (url, init) => { |
| 235 | const headers = headersFrom(init?.headers); |
| 236 | const requestWithoutBody = HttpClientRequest.make(httpMethodFrom(init?.method))(url, { |
| 237 | headers: recordFromHeaders(headers), |
| 238 | }); |
| 239 | const request = await applyBody(requestWithoutBody, headers, init?.body); |
| 240 | const effect = Effect.gen(function* () { |
| 241 | const client = yield* HttpClient.HttpClient; |
| 242 | const response = yield* client.execute(request); |
| 243 | const responseHeaders = new Headers(); |
| 244 | for (const [key, value] of Object.entries(response.headers)) { |
| 245 | if (value !== undefined) responseHeaders.set(key, value); |
| 246 | } |
| 247 | // Abort must reach the body, not just the pending request: this stream |
| 248 | // fiber outlives the `runPromise` below, so without this streamable |
| 249 | // http's SSE `GET` stays in flight after `close()`. Interrupted at the |
| 250 | // source because the SDK holds a locked reader on that same stream, |
| 251 | // which rules out cancelling the ReadableStream. |
| 252 | const stream = |
| 253 | init?.signal == null |
| 254 | ? response.stream |
| 255 | : Stream.interruptWhen(response.stream, awaitAbort(init.signal)); |
| 256 | const body = |
| 257 | response.status === 204 || response.status === 205 || response.status === 304 |
| 258 | ? null |
| 259 | : Stream.toReadableStream(stream); |
| 260 | return new Response(body, { |
| 261 | status: response.status, |
| 262 | headers: responseHeaders, |
| 263 | }); |
| 264 | }).pipe(Effect.mapError(normalizeHttpClientFailure), Effect.provide(httpClientLayer)); |
| 265 | // A 403 carrying an RFC 6750 insufficient_scope challenge is intercepted |
| 266 | // HERE, below the SDK: with an authProvider the SDK would consume the |
| 267 | // challenge and re-run auth ("upscoping"), which our static-token |
| 268 | // provider can only answer by demanding reauthorization — misclassifying |
| 269 | // an unfixable scope shortfall as oauth_reauth_required. Thrown as the |
| 270 | // tagged error from the fetch adapter (a true runtime edge: the SDK |
| 271 | // consumes promise rejections) so it reaches the invoke/connect catch |
| 272 | // sites verbatim. |
| 273 | const promise = Effect.runPromise(effect).then((response) => { |
| 274 | if (response.status === 403) { |
| 275 | const challenge = response.headers.get("www-authenticate"); |
| 276 | if ( |
| 277 | challenge !== null && |
| 278 | detectInsufficientScope({ headers: { "www-authenticate": challenge } }) !== null |
| 279 | ) { |
| 280 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: Fetch-compatible adapter can only signal through a rejected promise |
| 281 | throw new McpInsufficientScopeError({ |
| 282 | message: |
| 283 | "MCP server rejected the call: the OAuth grant does not cover the required scope", |
| 284 | }); |
| 285 | } |
| 286 | } |
| 287 | return response; |
| 288 | }); |
| 289 | // Mark the request promise observed (a no-op handler on the ORIGINAL |
| 290 | // promise; callers still see the rejection). The MCP SDK fires some |
| 291 | // requests without a rejection handler — a cancellation notification |
nothing calls this directly
no test coverage detected