( transport: string, message: string, cause: unknown, )
| 207 | }); |
| 208 | |
| 209 | const connectionFailure = ( |
| 210 | transport: string, |
| 211 | message: string, |
| 212 | cause: unknown, |
| 213 | ): McpConnectionError | McpOAuthReauthorizationRequired => { |
| 214 | if (Predicate.isTagged(cause, "McpOAuthReauthorizationRequired")) { |
| 215 | return new McpOAuthReauthorizationRequired({ message: "MCP OAuth re-authorization required" }); |
| 216 | } |
| 217 | if (Predicate.isTagged(cause, "McpInsufficientScopeError")) { |
| 218 | // Surfaced as a connection error with the 403 status; the invoke/connect |
| 219 | // catch sites detect the tag and classify as oauth_scope_insufficient. |
| 220 | return new McpConnectionError({ |
| 221 | transport, |
| 222 | message: `${message} (HTTP 403: insufficient scope)`, |
| 223 | httpStatus: 403, |
| 224 | insufficientScope: true, |
| 225 | }); |
| 226 | } |
| 227 | // Carry the handshake HTTP status structurally (and in the message for |
| 228 | // humans) so the liveness health check can classify a rejected credential |
| 229 | // as expired rather than a generic connection failure. |
| 230 | const status = httpStatusFromCause(cause); |
| 231 | return new McpConnectionError({ |
| 232 | transport, |
| 233 | message: status === undefined ? message : `${message} (HTTP ${status})`, |
| 234 | ...(status === undefined ? {} : { httpStatus: status }), |
| 235 | }); |
| 236 | }; |
| 237 | |
| 238 | const connectClient = (input: { |
| 239 | transport: string; |
no test coverage detected