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