All files / scripts/mcp-client/transport error-envelope.ts

85.71% Statements 12/14
78.94% Branches 15/19
100% Functions 2/2
84.61% Lines 11/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82                                                                              104x   3x   104x   104x     3x                     11x 11x   11x           11x         2x           11x    
/**
 * @module mcp-client/transport/error-envelope
 * @description JSON-RPC error → Error mapping plus request-failure message
 * formatting for the MCP transport.
 *
 * Extracted from `jsonrpc.ts` (Hack23/riksdagsmonitor#2578 follow-up) so the
 * wire-level dispatcher stays focused on orchestration and error wording is
 * unit-testable in isolation.
 *
 * @author Hack23 AB
 * @license Apache-2.0
 */
 
import type { JsonRpcResponse } from '../../types/mcp.js';
 
/**
 * Classification of a JSON-RPC error envelope returned by the MCP gateway.
 *
 *  - `none` — no `error` field; the call succeeded.
 *  - `session_init` — the gateway reports a stale session and the request
 *    should be retried after re-initialising. The session id MUST be cleared
 *    by the caller before retrying.
 *  - `fatal` — non-recoverable; the caller should throw the rendered message.
 */
export type JsonRpcErrorKind = 'none' | 'session_init' | 'fatal';
 
export interface JsonRpcErrorOutcome {
  kind: JsonRpcErrorKind;
  /** Pre-rendered, caller-ready error message (never empty when kind !== 'none'). */
  message: string;
}
 
/**
 * Examine a parsed JSON-RPC response and classify any embedded error. The
 * `session initialization` and `Too Many Requests` triggers signal that the
 * caller should null its `sessionId`, await a backoff, and replay the request
 * (bounded retry — typically 2 attempts).
 */
export function classifyJsonRpcError(response: JsonRpcResponse): JsonRpcErrorOutcome {
  if (!response.error) return { kind: 'none', message: '' };
 
  const errorMsg = response.error.message || JSON.stringify(response.error);
  const sessionTrigger =
    errorMsg.includes('session initialization') || errorMsg.includes('Too Many Requests');
 
  Iif (sessionTrigger) {
    return { kind: 'session_init', message: `MCP tool error: ${errorMsg}` };
  }
  return { kind: 'fatal', message: `MCP tool error: ${errorMsg}` };
}
 
/**
 * Compose the `MCP request failed: …` message with troubleshooting tips.
 *
 * Two hint blocks are emitted:
 *   - timeout/AbortError → cold-start guidance for Render.com free tier.
 *   - network/ECONNREFUSED/fetch failed → general connectivity guidance.
 */
export function formatRequestFailure(err: Error, baseURL: string): string {
  const errorMsg = (err.message ?? '').toLowerCase();
  let message = `MCP request failed: ${err.message}`;
 
  Iif (err.name === 'AbortError' || errorMsg.includes('timeout')) {
    message += `\n\n💡 Troubleshooting tips:
  - The MCP server may be cold starting (Render.com free tier)
  - Try increasing timeout or waiting a few minutes
  - Server URL: ${baseURL}
  - Consider running workflow again in 5-10 minutes`;
  } else if (
    errorMsg.includes('network') ||
    errorMsg.includes('econnrefused') ||
    errorMsg.includes('fetch failed')
  ) {
    message += `\n\n💡 Troubleshooting tips:
  - Check if MCP server is accessible: ${baseURL}
  - Verify network connectivity
  - The server may be temporarily unavailable
  - Try manual workflow dispatch with force_generation=true`;
  }
  return message;
}