Skip to main content
Glama
generate-custom.ts10.8 kB
import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { CustomGenerator } from '../generators/custom-generator.js'; import { SupportedLocale, PatternType, type RangePattern } from '../types/schema.js'; /** * Zod validation schema for range pattern values. * * @constant * @type {z.ZodObject} */ const RangePatternSchema = z.object({ min: z.number().describe('Minimum value'), max: z.number().describe('Maximum value'), precision: z.number().optional().describe('Decimal precision (0 for integers)'), }); /** * Zod validation schema for custom pattern definitions. * * @constant * @type {z.ZodObject} */ const CustomPatternSchema = z.object({ type: z.nativeEnum(PatternType).describe('Pattern type'), value: z .union([z.string(), z.array(z.string()), RangePatternSchema]) .describe('Pattern value (type depends on pattern type)'), }); /** * Zod validation schema for generate-custom tool parameters. * * @constant * @type {z.ZodObject} */ export const GenerateCustomSchema = z.object({ count: z.number().min(1).max(10000).default(1).describe('Number of records to generate'), patterns: z .record(z.string(), CustomPatternSchema) .refine((patterns) => Object.keys(patterns).length > 0, { message: 'At least one pattern must be defined', }) .describe('Map of field names to pattern definitions'), locale: z .nativeEnum(SupportedLocale) .default(SupportedLocale.EN) .describe('Locale for generated data (affects format-based patterns)'), seed: z.number().optional().describe('Optional seed for reproducible generation'), }); /** * Type definition for generate-custom parameters, inferred from Zod schema. * * @typedef {z.infer<typeof GenerateCustomSchema>} GenerateCustomParams */ export type GenerateCustomParams = z.infer<typeof GenerateCustomSchema>; /** * MCP Tool definition for custom pattern-based data generation. * Supports regex, enum, format, and range patterns. * * @constant * @type {Tool} * @property {string} name - Tool identifier * @property {string} description - Human-readable tool description * @property {Object} inputSchema - JSON Schema for tool inputs */ export const generateCustomTool: Tool = { name: 'generate-custom', description: 'Generates fake data following custom patterns, including regex patterns, enums, formats, and ranges', inputSchema: zodToJsonSchema(GenerateCustomSchema) as Tool['inputSchema'], }; /** * Handler function for the generate-custom MCP tool. * Validates pattern definitions, generates custom data following patterns, * and returns formatted MCP response. * * @async * @param {unknown} args - Raw arguments from MCP client (validated against schema) * @returns {Promise<{ content: unknown[] }>} MCP-formatted response with generated data * @throws {Error} If parameter validation fails, patterns are invalid, or generation encounters an error * @example * ```typescript * const result = await handleGenerateCustom({ * count: 100, * patterns: { * orderId: { type: 'regex', value: 'ORD-[A-Z]{3}-\\d{4}' }, * status: { type: 'enum', value: ['pending', 'shipped', 'delivered'] }, * amount: { type: 'range', value: { min: 10, max: 1000, precision: 2 } } * }, * seed: 12345 * }); * ``` */ export function handleGenerateCustom(args: unknown): Promise<{ content: unknown[] }> { const startTime = Date.now(); try { // Validate and parse arguments const params = GenerateCustomSchema.parse(args); // Validate patterns validatePatterns(params.patterns); // Create generator const generator = new CustomGenerator({ seed: params.seed, locale: params.locale, }); // Generate data const data = params.count === 1 ? [generator.generate({ patterns: params.patterns })] : generator.generateMany(params.count, { patterns: params.patterns }); const generationTimeMs = Date.now() - startTime; // Build response const metadata = { count: data.length, patternCount: Object.keys(params.patterns).length, seed: generator.getSeed(), locale: generator.getLocale(), generationTimeMs, }; const responseText = params.seed ? `Generated ${data.length} custom record${data.length > 1 ? 's' : ''} with seed ${params.seed}` : `Generated ${data.length} custom record${data.length > 1 ? 's' : ''}`; return Promise.resolve({ content: [ { type: 'text', text: responseText, }, { type: 'resource', resource: { uri: 'faker://custom/generated', mimeType: 'application/json', text: JSON.stringify({ data, metadata }, null, 2), }, }, ], }); } catch (error) { if (error instanceof z.ZodError) { throw new Error( `Invalid parameters: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}` ); } throw error; } } /** * Validates all pattern definitions in a patterns map. * Checks each pattern type (regex, enum, format, range) for correctness. * * @param {Record<string, { type: PatternType; value: string | string[] | RangePattern }>} patterns - Map of field names to patterns * @throws {Error} If any pattern is invalid, with field name and specific error * @example * ```typescript * validatePatterns({ * code: { type: PatternType.REGEX, value: '[A-Z]{3}' }, * status: { type: PatternType.ENUM, value: ['active', 'inactive'] } * }); * ``` */ function validatePatterns( patterns: Record<string, { type: PatternType; value: string | string[] | RangePattern }> ): void { for (const [fieldName, pattern] of Object.entries(patterns)) { try { switch (pattern.type) { case PatternType.REGEX: validateRegexPattern(pattern.value as string); break; case PatternType.ENUM: validateEnumPattern(pattern.value as string[]); break; case PatternType.FORMAT: validateFormatPattern(pattern.value as string); break; case PatternType.RANGE: validateRangePattern(pattern.value as RangePattern); break; default: throw new Error(`Unsupported pattern type`); } } catch (error) { throw new Error(`Invalid pattern for field '${fieldName}': ${(error as Error).message}`); } } } /** * Validates a regex pattern string by attempting to compile it. * * @param {string} regexValue - The regex pattern to validate * @throws {Error} If the regex pattern is empty or has invalid syntax * @example * ```typescript * validateRegexPattern('[A-Z]{3}-\\d{4}'); // Valid * validateRegexPattern('[invalid'); // Throws error * ``` */ function validateRegexPattern(regexValue: string): void { if (typeof regexValue !== 'string' || regexValue.length === 0) { throw new Error('Regex pattern must be a non-empty string'); } try { new RegExp(regexValue); } catch (error) { throw new Error(`Invalid regex syntax: ${(error as Error).message}`); } } /** * Validates an enum pattern array. * Ensures the array is non-empty and contains only string values. * * @param {string[]} enumValues - Array of enum values * @throws {Error} If the array is empty, not an array, or contains non-string values * @example * ```typescript * validateEnumPattern(['active', 'inactive', 'pending']); // Valid * validateEnumPattern([]); // Throws error * validateEnumPattern([1, 2, 3]); // Throws error (not strings) * ``` */ function validateEnumPattern(enumValues: string[]): void { if (!Array.isArray(enumValues)) { throw new Error('Enum pattern must be an array'); } if (enumValues.length === 0) { throw new Error('Enum pattern must have at least one value'); } if (!enumValues.every((v) => typeof v === 'string')) { throw new Error('Enum pattern must contain only string values'); } } /** * Validates a format pattern string with placeholder syntax. * Supports {{year}}, {{random:N}}, and {{number:N}} placeholders. * * @param {string} formatValue - The format template string * @throws {Error} If the format is empty or contains invalid placeholders * @example * ```typescript * validateFormatPattern('USER-{{number:4}}-{{year}}'); // Valid * validateFormatPattern('CODE-{{random:5}}'); // Valid * validateFormatPattern(''); // Throws error * ``` */ function validateFormatPattern(formatValue: string): void { if (typeof formatValue !== 'string' || formatValue.length === 0) { throw new Error('Format pattern must be a non-empty string'); } // Check for valid placeholder syntax const placeholderRegex = /\{\{(year|random:\d+|number:\d+)\}\}/g; const placeholders = formatValue.match(placeholderRegex); if (placeholders) { // Validate each placeholder for (const placeholder of placeholders) { if (placeholder.includes('random:') || placeholder.includes('number:')) { const lengthMatch = placeholder.match(/:(\d+)\}\}/); if (lengthMatch && lengthMatch[1]) { const length = parseInt(lengthMatch[1], 10); if (length <= 0 || length > 100) { throw new Error( `Invalid placeholder length in '${placeholder}': must be between 1 and 100` ); } } } } } } /** * Validates a range pattern object. * Ensures min/max are numbers, min <= max, and precision is valid. * * @param {RangePattern} rangeValue - The range pattern to validate * @throws {Error} If the range is invalid (min > max, missing values, invalid precision) * @example * ```typescript * validateRangePattern({ min: 0, max: 100 }); // Valid integer range * validateRangePattern({ min: 0, max: 1, precision: 2 }); // Valid float range * validateRangePattern({ min: 100, max: 0 }); // Throws error (min > max) * ``` */ function validateRangePattern(rangeValue: RangePattern): void { if (typeof rangeValue !== 'object' || rangeValue === null) { throw new Error('Range pattern must be an object'); } if (typeof rangeValue.min !== 'number' || typeof rangeValue.max !== 'number') { throw new Error('Range pattern must have numeric min and max values'); } if (rangeValue.min > rangeValue.max) { throw new Error( `Range pattern invalid: min (${rangeValue.min}) must be less than or equal to max (${rangeValue.max})` ); } if (rangeValue.precision !== undefined) { if (typeof rangeValue.precision !== 'number' || rangeValue.precision < 0) { throw new Error('Range pattern precision must be a non-negative number'); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/funsjanssen/faker-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server