/**
* Population Tool
*
* Application layer tool for fetching US Census population data.
* Orchestrates domain logic and infrastructure services.
* Uses Zod for runtime type validation and schema generation.
*/
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { buildStateQuery, parseCensusResponse, formatCensusData, createSuccessResult, } from '../../domain/census-service.js';
/**
* Tool name constant
*/
export const POPULATION_TOOL_NAME = 'get-population';
/**
* Zod schema for population tool input
* Provides runtime validation and type inference
*/
export const PopulationToolInputSchema = z.object({
states: z
.array(z.number().int().min(0).max(56))
.min(1)
.describe('Array of FIPS state codes (e.g., [1, 6, 36]). Use [0] for all states.'),
});
/**
* Get tool definition for MCP server
* Uses Zod schema converted to JSON Schema
*
* @returns Tool definition with schema generated from Zod
*/
export function getPopulationToolDefinition() {
// Convert Zod schema to JSON Schema
const jsonSchema = zodToJsonSchema(PopulationToolInputSchema, {
name: 'PopulationToolInput',
$refStrategy: 'none',
});
// Extract the actual object schema from the definitions
// zodToJsonSchema returns a root schema with $ref and definitions
// MCP expects the inputSchema to be the actual object schema
const actualSchema = jsonSchema.definitions?.PopulationToolInput || jsonSchema;
return {
name: POPULATION_TOOL_NAME,
description: 'Get population data for US states by FIPS state code. Pass an array of state codes (e.g., [1, 6, 36] for Alabama, California, New York) or use [0] to get all states.',
inputSchema: actualSchema,
};
}
/**
* Population tool handler
*/
export class PopulationTool {
censusClient;
logger;
constructor(censusClient, logger) {
this.censusClient = censusClient;
this.logger = logger;
}
/**
* Execute the population tool
* Uses Zod for runtime validation
*
* @param args - Tool arguments (validated against Zod schema)
* @returns Tool result with formatted data
*/
async execute(args) {
this.logger.info('Executing population tool', { args });
// Validate and parse using Zod schema
const parseResult = PopulationToolInputSchema.safeParse(args);
if (!parseResult.success) {
const errorMessages = parseResult.error.errors
.map((err) => `${err.path.join('.')}: ${err.message}`)
.join(', ');
this.logger.warn('Invalid population request', {
error: errorMessages,
args,
zodErrors: parseResult.error.errors,
});
return this.createErrorToolResult(`Invalid request parameters: ${errorMessages}`);
}
// Type-safe validated input
const validatedInput = parseResult.data;
// Build query and fetch data
const stateQuery = buildStateQuery(validatedInput.states);
this.logger.debug('Fetching population data', { stateQuery });
const rawData = await this.censusClient.getPopulationData(stateQuery);
if (!rawData) {
this.logger.error('Failed to retrieve census data', undefined, {
stateQuery,
});
return this.createErrorToolResult('Failed to retrieve census data');
}
if (rawData.length === 0) {
this.logger.warn('No census data available', { stateQuery });
return this.createErrorToolResult('No census data available');
}
// Parse and format data
try {
const parsedData = parseCensusResponse(rawData);
const formattedOutput = formatCensusData(parsedData, rawData);
const result = createSuccessResult(parsedData, formattedOutput);
this.logger.info('Population data retrieved successfully', {
recordCount: parsedData.length,
stateQuery,
states: validatedInput.states,
});
return {
content: [
{
type: 'text',
text: result.formattedOutput || '',
},
],
};
}
catch (error) {
this.logger.error('Failed to parse census data', error instanceof Error ? error : new Error(String(error)), { stateQuery });
return this.createErrorToolResult('Failed to parse census data');
}
}
/**
* Create error tool result
*
* @param message - Error message
* @returns Tool result with error
*/
createErrorToolResult(message) {
return {
content: [
{
type: 'text',
text: message,
},
],
};
}
}
/**
* Factory function to create population tool
*/
export function createPopulationTool(censusClient, logger) {
return new PopulationTool(censusClient, logger);
}