All files / src/mcp wb-mcp-client.ts

100% Statements 27/27
96.55% Branches 28/29
100% Functions 4/4
100% Lines 27/27

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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153                                                2x     2x     2x           2x                     2x                                                       33x                                     9x 5x 5x   4x 4x         3x 3x 3x           2x                         8x 7x         7x 7x 7x 6x   1x 1x     7x             17x 6x 6x      
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module MCP/WBMCPClient
 * @description World Bank MCP client — domain-specific tool wrappers for
 * the World Bank MCP server ({@link https://github.com/anshumax/world_bank_mcp_server}).
 *
 * Extends {@link MCPConnection} with World Bank-specific tool methods.
 * Provides economic indicator data for EU member states to enrich
 * European Parliament news articles with macroeconomic context.
 *
 * Environment variables:
 * - `WB_MCP_SERVER_PATH` — Override default server binary path
 * - `WB_MCP_GATEWAY_URL` — Use HTTP gateway transport instead of stdio
 * - `WB_MCP_GATEWAY_API_KEY` — API key for gateway authentication
 */
 
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { MCPConnection } from './mcp-connection.js';
import type { MCPToolResult, MCPClientOptions } from '../types/index.js';
 
/** npm binary name for the World Bank MCP server */
const WB_BINARY_NAME = 'worldbank-mcp';
 
/** Platform-specific binary filename (Windows uses .cmd shim) */
const WB_BINARY_FILE = process.platform === 'win32' ? `${WB_BINARY_NAME}.cmd` : WB_BINARY_NAME;
 
/** Default binary resolved from node_modules/.bin relative to this file's compiled location */
const WB_DEFAULT_SERVER = resolve(
  dirname(fileURLToPath(import.meta.url)),
  `../../node_modules/.bin/${WB_BINARY_FILE}`
);
 
/** Fallback payload when indicator data is unavailable (empty CSV) */
const INDICATOR_FALLBACK = '';
 
/**
 * Canonical list of tools exposed by the World Bank MCP gateway. The news
 * workflows, probe script, and the integration test suite all reference this
 * list so a regression that adds/removes a tool fails a single drift guard
 * (`test/integration/mcp/worldbank-mcp.test.js`) instead of silently breaking
 * prompt/validator/probe coverage.
 *
 * Kept in sync with `analysis/methodologies/worldbank-indicator-mapping.md`.
 */
export const WORLD_BANK_MCP_TOOLS: readonly string[] = [
  'search-indicators',
  'get-countries',
  'get-country-info',
  'get-economic-data',
  'get-social-data',
  'get-education-data',
  'get-health-data',
];
 
/**
 * MCP Client for World Bank economic data access.
 * Extends {@link MCPConnection} with World Bank-specific tool wrapper methods.
 *
 * Always supplies an explicit World Bank server path so the base class never
 * falls back to the European Parliament MCP server defaults.
 */
export class WorldBankMCPClient extends MCPConnection {
  /**
   * Create a new World Bank MCP client. Always supplies an explicit World
   * Bank server path, gateway URL, and label so the base class never falls
   * back to European Parliament defaults.
   *
   * @param options - Connection options. `serverPath` / `gatewayUrl` /
   *   `gatewayApiKey` / `serverLabel` are filled from `WB_*` environment
   *   variables when not provided explicitly.
   */
  constructor(options: MCPClientOptions = {}) {
    super({
      ...options,
      serverPath: options.serverPath ?? process.env['WB_MCP_SERVER_PATH'] ?? WB_DEFAULT_SERVER,
      gatewayUrl: options.gatewayUrl ?? process.env['WB_MCP_GATEWAY_URL'] ?? '',
      gatewayApiKey: options.gatewayApiKey ?? process.env['WB_MCP_GATEWAY_API_KEY'] ?? '',
      serverLabel: options.serverLabel ?? 'World Bank MCP Server',
    });
  }
 
  /**
   * Get economic indicator data for a specific country.
   *
   * Calls the `get_indicator_for_country` tool on the World Bank MCP server.
   *
   * @param countryId - World Bank country code (e.g., 'DEU' for Germany, 'FRA' for France)
   * @param indicatorId - World Bank indicator ID (e.g., 'NY.GDP.MKTP.CD' for GDP)
   * @returns MCP tool result with CSV-formatted indicator data, or empty text on error
   */
  async getIndicatorForCountry(countryId: string, indicatorId: string): Promise<MCPToolResult> {
    if (!countryId || !indicatorId) {
      console.warn('get_indicator_for_country called without required countryId or indicatorId');
      return { content: [{ type: 'text', text: INDICATOR_FALLBACK }] };
    }
    try {
      return await this.callTool('get_indicator_for_country', {
        country_id: countryId,
        indicator_id: indicatorId,
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      console.warn('get_indicator_for_country not available:', message);
      return { content: [{ type: 'text', text: INDICATOR_FALLBACK }] };
    }
  }
}
 
/** Singleton World Bank MCP client instance */
let wbClientInstance: WorldBankMCPClient | null = null;
 
/**
 * Get or create singleton World Bank MCP client instance.
 *
 * Uses `WB_MCP_SERVER_PATH`, `WB_MCP_GATEWAY_URL`, and `WB_MCP_GATEWAY_API_KEY`
 * environment variables for configuration. Falls back to stdio transport
 * with the `worldbank-mcp` npm binary resolved from `node_modules/.bin`.
 *
 * @param options - Client options (overrides env vars)
 * @returns Connected World Bank MCP client
 */
export async function getWBMCPClient(options: MCPClientOptions = {}): Promise<WorldBankMCPClient> {
  if (!wbClientInstance) {
    const mergedOptions: MCPClientOptions = {
      ...options,
      maxConnectionAttempts: options.maxConnectionAttempts ?? 2,
      connectionRetryDelay: options.connectionRetryDelay ?? 1000,
    };
    const client = new WorldBankMCPClient(mergedOptions);
    try {
      await client.connect();
      wbClientInstance = client;
    } catch (error) {
      wbClientInstance = null;
      throw error;
    }
  }
  return wbClientInstance;
}
 
/**
 * Close and cleanup singleton World Bank MCP client
 */
export async function closeWBMCPClient(): Promise<void> {
  if (wbClientInstance) {
    wbClientInstance.disconnect();
    wbClientInstance = null;
  }
}