( transport: string, message: string, cause: unknown, )
| 331 | }); |
| 332 | |
| 333 | const connectionFailure = ( |
| 334 | transport: string, |
| 335 | message: string, |
| 336 | cause: unknown, |
| 337 | ): McpConnectionError | McpOAuthReauthorizationRequired => { |
| 338 | if (Predicate.isTagged(cause, "McpOAuthReauthorizationRequired")) { |
| 339 | return new McpOAuthReauthorizationRequired({ message: "MCP OAuth re-authorization required" }); |
| 340 | } |
| 341 | if (Predicate.isTagged(cause, "McpInsufficientScopeError")) { |
| 342 | // Surfaced as a connection error with the 403 status; the invoke/connect |
| 343 | // catch sites detect the tag and classify as oauth_scope_insufficient. |
| 344 | return new McpConnectionError({ |
| 345 | transport, |
| 346 | message: `${message} (HTTP 403: insufficient scope)`, |
| 347 | httpStatus: 403, |
| 348 | insufficientScope: true, |
| 349 | }); |
| 350 | } |
| 351 | const httpTransportError = nestedMcpHttpTransportError(cause); |
| 352 | if (Option.isSome(httpTransportError)) { |
| 353 | return new McpConnectionError({ |
| 354 | transport, |
| 355 | message: CONNECTION_FAILURE_MESSAGES[httpTransportError.value.failureKind], |
| 356 | failureKind: httpTransportError.value.failureKind, |
| 357 | }); |
| 358 | } |
| 359 | // Carry the handshake HTTP status structurally (and in the message for |
| 360 | // humans) so the liveness health check can classify a rejected credential |
| 361 | // as expired rather than a generic connection failure. |
| 362 | const status = connectionHttpStatusFromCause(cause); |
| 363 | const failureKind: McpConnectionFailureKind = |
| 364 | isStreamableHttpProtocolError(cause) || |
| 365 | (status !== undefined && PROTOCOL_HTTP_STATUSES.has(status)) |
| 366 | ? "protocol" |
| 367 | : status === undefined |
| 368 | ? "network" |
| 369 | : "http"; |
| 370 | return new McpConnectionError({ |
| 371 | transport, |
| 372 | message: status === undefined ? message : `${message} (HTTP ${status})`, |
| 373 | failureKind, |
| 374 | ...(status === undefined ? {} : { httpStatus: status }), |
| 375 | }); |
| 376 | }; |
| 377 | |
| 378 | const connectionAttemptSummary = (failure: McpConnectionError): string => { |
| 379 | if (failure.httpStatus !== undefined) return `HTTP ${failure.httpStatus}`; |
no test coverage detected