compare_eu_economies
Fetch the latest inflation, GDP growth, and unemployment for 2-10 EU countries in one parallel call to Eurostat. Compare economic indicators across countries to build dashboards or run multi-metric analyses without multiple API requests.
Instructions
Fetches the latest inflation, GDP growth, and unemployment for 2-10 EU countries in a single parallel call to Eurostat. Returns a JSON object with: countries (array of per-country snapshots, each containing country as full name, country_code, inflation as {rate, period} or null, gdp_growth as {rate, period} or null, unemployment as {rate, period} or null), source, and retrieved_at as ISO 8601. Each indicator returns the single most recent available value. Inflation period is YYYY-MM; GDP period is YYYY-Qq; unemployment period is YYYY-MM. Data is cached 24 hours. USAGE: Prefer this tool over calling get_eu_inflation, get_eu_gdp, and get_eu_unemployment separately when building dashboards or running multi-indicator comparisons — it reduces latency by parallelizing the three Eurostat requests. Each indicator's period may differ due to different Eurostat release schedules (inflation lags ~30 days, GDP ~90 days). An indicator field is null when Eurostat has not published recent data — always handle null gracefully. Use the indicators parameter to skip unneeded metrics and reduce API calls.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| countries | Yes | 2-10 EU country codes to compare (e.g. ["DE", "FR", "ES", "IT"]). Use "EA20" for Eurozone. | |
| indicators | No | Which indicators to include. Default: all three. |
Implementation Reference
- src/tools/compare-economies.ts:81-155 (handler)Main handler function that registers the compare_eu_economies tool on the MCP server. Fetches inflation, GDP growth, and unemployment for 2-10 EU countries in parallel from Eurostat, with caching and error handling.
export function registerCompareEconomiesTool(server: McpServer): void { server.tool( 'compare_eu_economies', 'Fetches the latest inflation, GDP growth, and unemployment for 2-10 EU countries in a single parallel call to Eurostat. Returns a JSON object with: `countries` (array of per-country snapshots, each containing `country` as full name, `country_code`, `inflation` as `{rate, period}` or null, `gdp_growth` as `{rate, period}` or null, `unemployment` as `{rate, period}` or null), `source`, and `retrieved_at` as ISO 8601. Each indicator returns the single most recent available value. Inflation period is YYYY-MM; GDP period is YYYY-Qq; unemployment period is YYYY-MM. Data is cached 24 hours. USAGE: Prefer this tool over calling get_eu_inflation, get_eu_gdp, and get_eu_unemployment separately when building dashboards or running multi-indicator comparisons — it reduces latency by parallelizing the three Eurostat requests. Each indicator\'s period may differ due to different Eurostat release schedules (inflation lags ~30 days, GDP ~90 days). An indicator field is null when Eurostat has not published recent data — always handle null gracefully. Use the `indicators` parameter to skip unneeded metrics and reduce API calls.', { countries: z .array(z.string().min(2).max(12).toUpperCase()) .min(2) .max(10) .describe('2-10 EU country codes to compare (e.g. ["DE", "FR", "ES", "IT"]). Use "EA20" for Eurozone.'), indicators: z .array(z.enum(['inflation', 'gdp_growth', 'unemployment'])) .optional() .default(['inflation', 'gdp_growth', 'unemployment']) .describe('Which indicators to include. Default: all three.'), }, READ_ONLY_PUBLIC_API, async ({ countries, indicators }) => { return withMcpMiddleware({ serverName: SERVER_NAME, toolName: 'compare_eu_economies' }, async () => { const params = { countries, indicators }; const cacheKey = `compare_eu_economies:${hashParams(params as Record<string, unknown>)}`; const cached = await cacheGet<CompareEconomiesResult>(cacheKey); if (cached) { return { content: [{ type: 'text' as const, text: JSON.stringify(cached, null, 2) }] }; } const activeIndicators = indicators ?? ['inflation', 'gdp_growth', 'unemployment']; type IndicatorMap = Record<string, { value: number; period: string }>; const empty: IndicatorMap = {}; // Fetch all requested indicators in parallel const [inflationData, gdpData, unemploymentData] = await Promise.all([ activeIndicators.includes('inflation') ? fetchLatestEurostatValue('prc_hicp_manr', { coicop: 'CP00', unit: 'RCH_A' }, countries) : Promise.resolve(empty), activeIndicators.includes('gdp_growth') ? fetchLatestEurostatValue('namq_10_gdp', { na_item: 'B1GQ', unit: 'CLV_PCH_SM' }, countries) : Promise.resolve(empty), activeIndicators.includes('unemployment') ? fetchLatestEurostatValue('une_rt_m', { sex: 'T', age: 'TOTAL', unit: 'PC_ACT', s_adj: 'SA' }, countries) : Promise.resolve(empty), ]); const snapshots: CountrySnapshot[] = countries.map((code) => ({ country: countryName(code), country_code: code, inflation: inflationData[code] ? { rate: inflationData[code].value, period: inflationData[code].period } : null, gdp_growth: gdpData[code] ? { rate: gdpData[code].value, period: gdpData[code].period } : null, unemployment: unemploymentData[code] ? { rate: unemploymentData[code].value, period: unemploymentData[code].period } : null, })); if (snapshots.every((s) => !s.inflation && !s.gdp_growth && !s.unemployment)) { return makeMcpError('No economic data found for the requested countries', 'SOURCE_UNAVAILABLE'); } const result: CompareEconomiesResult = { countries: snapshots, source: 'Eurostat (HICP, namq_10_gdp, une_rt_m)', retrieved_at: new Date().toISOString(), }; await cacheSet(cacheKey, result, CACHE_TTL); return { content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }] }; }); }, ); } - src/tools/compare-economies.ts:10-22 (schema)TypeScript interfaces defining the schema: CountrySnapshot (per-country data) and CompareEconomiesResult (full result envelope).
interface CountrySnapshot { country: string; country_code: string; inflation?: { rate: number; period: string } | null; gdp_growth?: { rate: number; period: string } | null; unemployment?: { rate: number; period: string } | null; } interface CompareEconomiesResult { countries: CountrySnapshot[]; source: string; retrieved_at: string; } - src/tools/compare-economies.ts:85-96 (schema)Zod input schema for the tool: 'countries' (array of 2-10 uppercase country codes) and optional 'indicators' (subset of inflation/gdp_growth/unemployment).
{ countries: z .array(z.string().min(2).max(12).toUpperCase()) .min(2) .max(10) .describe('2-10 EU country codes to compare (e.g. ["DE", "FR", "ES", "IT"]). Use "EA20" for Eurozone.'), indicators: z .array(z.enum(['inflation', 'gdp_growth', 'unemployment'])) .optional() .default(['inflation', 'gdp_growth', 'unemployment']) .describe('Which indicators to include. Default: all three.'), }, - src/http-entry.ts:41-47 (registration)Registration of tool name 'compare_eu_economies' in the MCP_WELL_KNOWN tools list.
tools: [ 'get_ecb_rates', 'get_euro_exchange', 'get_eu_inflation', 'get_eu_gdp', 'get_eu_unemployment', 'compare_eu_economies' - src/http-entry.ts:16-30 (registration)Import and registration call of registerCompareEconomiesTool on the McpServer instance.
import { registerCompareEconomiesTool } from './tools/compare-economies.js'; const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : (() => { console.error('FATAL: PORT env var is required.'); process.exit(1); })() as never; function createMcpServer(): McpServer { const server = new McpServer({ name: NAME, version: VERSION }); registerEcbRatesTool(server); registerEuroExchangeTool(server); registerEuInflationTool(server); registerEuGdpTool(server); registerEuUnemploymentTool(server); registerCompareEconomiesTool(server); - src/tools/compare-economies.ts:24-79 (helper)fetchLatestEurostatValue helper: fetches a single Eurostat dataset for given countries and returns latest values.
async function fetchLatestEurostatValue( dataset: string, params: Record<string, string>, countries: string[], ): Promise<Record<string, { value: number; period: string }>> { const searchParams = new URLSearchParams({ ...params, lastTimePeriod: '1', format: 'JSON', lang: 'EN', }); for (const country of countries) { searchParams.append('geo', country); } const res = await fetch(`${EUROSTAT_BASE}/${dataset}?${searchParams}`, { signal: AbortSignal.timeout(15_000), }); if (!res.ok) return {}; const json = (await res.json()) as { value: Record<string, number | null>; dimension: { geo: { category: { index: Record<string, number>; label: Record<string, string> } }; time: { category: { index: Record<string, number> } }; }; size: number[]; }; const geoIndex = json.dimension.geo.category.index; const timeIndex = json.dimension.time.category.index; const timeByPos: Record<number, string> = {}; for (const [period, pos] of Object.entries(timeIndex)) { timeByPos[pos] = period; } const timeSize = json.size[json.size.length - 1]; const result: Record<string, { value: number; period: string }> = {}; for (const country of countries) { const geoPos = geoIndex[country]; if (geoPos === undefined) continue; for (let t = timeSize - 1; t >= 0; t--) { const valueIndex = geoPos * timeSize + t; const value = json.value[String(valueIndex)]; if (value !== null && value !== undefined) { result[country] = { value, period: timeByPos[t] ?? 'unknown' }; break; } } } return result; } - countryName helper: maps country codes to full names via a lookup table.
const COUNTRY_NAMES: Record<string, string> = { AT: 'Austria', BE: 'Belgium', BG: 'Bulgaria', CY: 'Cyprus', CZ: 'Czechia', DE: 'Germany', DK: 'Denmark', EE: 'Estonia', ES: 'Spain', FI: 'Finland', FR: 'France', GR: 'Greece', HR: 'Croatia', HU: 'Hungary', IE: 'Ireland', IT: 'Italy', LT: 'Lithuania', LU: 'Luxembourg', LV: 'Latvia', MT: 'Malta', NL: 'Netherlands', PL: 'Poland', PT: 'Portugal', RO: 'Romania', SE: 'Sweden', SI: 'Slovenia', SK: 'Slovakia', EA20: 'Eurozone (EA20)', EA: 'Eurozone', EU27_2020: 'European Union (EU-27)', }; function countryName(code: string): string { return COUNTRY_NAMES[code] ?? code; }