All files / scripts/mcp-client/transport request-builder.ts

83.33% Statements 10/12
81.81% Branches 9/11
80% Functions 4/5
90% Lines 9/10

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                                  32x       123x                   130x 8x                       122x                                   122x         122x 122x 122x    
/**
 * @module mcp-client/transport/request-builder
 * @description Pure helpers for constructing JSON-RPC 2.0 request envelopes
 * and HTTP request headers for the MCP transport.
 *
 * Extracted from `jsonrpc.ts` (Hack23/riksdagsmonitor#2578 follow-up) so the
 * wire-level dispatcher stays focused on request/response orchestration.
 * These helpers are deterministic (id allocator aside) and trivially unit-
 * testable in isolation.
 *
 * @author Hack23 AB
 * @license Apache-2.0
 */
 
import type { JsonRpcRequest } from '../../types/mcp.js';
 
/** Module-scoped JSON-RPC id counter — monotonically increasing across instances. */
let jsonRpcId = 1;
 
/** Returns the next monotonic JSON-RPC id and post-increments the counter. */
export function nextJsonRpcId(): number {
  return jsonRpcId++;
}
 
/** Test-only reset of the id counter. Not part of the public API. */
export function _resetJsonRpcIdForTests(value = 1): void {
  jsonRpcId = value;
}
 
/** Validate that an MCP tool name only contains safe characters. */
export function assertValidToolName(tool: string): void {
  if (!tool || typeof tool !== 'string' || !/^[a-zA-Z0-9_-]+$/.test(tool)) {
    throw new Error(
      `Invalid tool name: ${tool}. Tool names must contain only alphanumeric characters, hyphens, and underscores.`,
    );
  }
}
 
/** Build a `tools/call` JSON-RPC 2.0 request envelope. */
export function buildJsonRpcRequest(
  tool: string,
  params: Record<string, unknown>,
  id: number = nextJsonRpcId(),
): JsonRpcRequest {
  return {
    jsonrpc: '2.0',
    id,
    method: 'tools/call',
    params: { name: tool, arguments: params },
  };
}
 
/**
 * Build the HTTP request headers for an MCP JSON-RPC POST. Runtime headers
 * (`Authorization`, `Mcp-Session-Id`) always override any same-named keys
 * in `customHeaders` from the constructor.
 */
export function buildRequestHeaders(
  customHeaders: Readonly<Record<string, string>>,
  authToken: string | null | undefined,
  sessionId: string | null | undefined,
): Record<string, string> {
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    Accept: 'application/json, text/event-stream',
    ...customHeaders,
  };
  if (authToken) headers['Authorization'] = authToken;
  Iif (sessionId) headers['Mcp-Session-Id'] = sessionId;
  return headers;
}