Skip to main content
Glama
rycid

RandomUser MCP Server

by rycid
index.ts18.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; // Supported nationalities const NATIONALITIES = [ 'AU', 'BR', 'CA', 'CH', 'DE', 'DK', 'ES', 'FI', 'FR', 'GB', 'IE', 'IN', 'IR', 'MX', 'NL', 'NO', 'NZ', 'RS', 'TR', 'UA', 'US' ] as const; type Nationality = typeof NATIONALITIES[number]; interface GenderCounts { male: number; female: number; } interface NationalityWeights { [key: string]: number; } interface FieldOptions { name?: boolean; phone?: boolean; email?: boolean; location?: boolean; picture?: boolean; dob?: boolean; login?: boolean; registered?: boolean; id?: boolean; cell?: boolean; nat?: boolean; } interface FormatOptions { type: 'json' | 'csv' | 'sql' | 'xml'; sql?: { dialect: 'mysql' | 'postgresql' | 'sqlite'; tableName: string; includeCreate: boolean; }; csv?: { delimiter: ',' | ';' | '\t'; includeHeader: boolean; }; structure?: { flattenObjects: boolean; arrayFormat: 'brackets' | 'comma' | 'numbered'; dateFormat: 'iso' | 'unix' | 'formatted'; nameFormat: 'full' | 'first_last' | 'separate'; nullValues: 'empty' | 'null' | 'omit'; }; } class RandomUserServer { private server: Server; private axiosInstance; constructor() { this.server = new Server( { name: 'randomuser-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.axiosInstance = axios.create({ baseURL: 'https://randomuser.me/api/', }); this.setupTools(); this.setupErrorHandling(); } private setupTools() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'get_random_user', description: 'Get a single random user', inputSchema: { type: 'object', properties: { gender: { type: 'string', enum: ['male', 'female'], description: 'Filter results by gender' }, nationality: { type: 'string', enum: NATIONALITIES, description: 'Specify nationality' }, fields: { type: 'object', description: 'Specify which fields to include', properties: this.getFieldProperties() }, format: this.getFormatOptionsSchema(), password: { type: 'object', properties: this.getPasswordOptionsSchema() } } } }, { name: 'get_multiple_users', description: 'Get multiple random users', inputSchema: { type: 'object', properties: { count: { type: 'number', minimum: 1, maximum: 5000, description: 'Number of users to generate' }, gender: { type: 'string', enum: ['male', 'female'] }, nationality: { oneOf: [ { type: 'string', enum: NATIONALITIES }, { type: 'array', items: { type: 'string', enum: NATIONALITIES } } ] }, nationalityWeights: { type: 'object', patternProperties: { "^[A-Z]{2}$": { type: 'number', minimum: 0, maximum: 1 } } }, fields: { type: 'object', properties: this.getFieldProperties() }, format: this.getFormatOptionsSchema(), password: { type: 'object', properties: this.getPasswordOptionsSchema() } }, required: ['count'] } } ] })); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { switch (request.params.name) { case 'get_random_user': return this.handleGetRandomUser(request.params.arguments); case 'get_multiple_users': return this.handleGetMultipleUsers(request.params.arguments); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } private getFieldProperties() { return { mode: { type: 'string', enum: ['include', 'exclude'], default: 'include' }, values: { type: 'array', items: { type: 'string', enum: [ 'name', 'phone', 'email', 'location', 'picture', 'dob', 'login', 'registered', 'id', 'cell', 'nat' ] } } }; } private getFormatOptionsSchema() { return { type: 'object', properties: { type: { type: 'string', enum: ['json', 'csv', 'sql', 'xml'], default: 'json' }, sql: { type: 'object', properties: { dialect: { type: 'string', enum: ['mysql', 'postgresql', 'sqlite'] }, tableName: { type: 'string' }, includeCreate: { type: 'boolean' } } }, csv: { type: 'object', properties: { delimiter: { type: 'string', enum: [',', ';', '\t'] }, includeHeader: { type: 'boolean' } } }, structure: { type: 'object', properties: { flattenObjects: { type: 'boolean' }, arrayFormat: { type: 'string', enum: ['brackets', 'comma', 'numbered'] }, dateFormat: { type: 'string', enum: ['iso', 'unix', 'formatted'] }, nameFormat: { type: 'string', enum: ['full', 'first_last', 'separate'] }, nullValues: { type: 'string', enum: ['empty', 'null', 'omit'] } } } } }; } private getPasswordOptionsSchema() { return { charsets: { type: 'array', items: { type: 'string', enum: ['special', 'upper', 'lower', 'number'] }, description: 'Character sets to include in password' }, minLength: { type: 'number', minimum: 8, maximum: 64, description: 'Minimum password length (8-64)' }, maxLength: { type: 'number', minimum: 8, maximum: 64, description: 'Maximum password length (8-64)' } }; } private formatResults(results: any[], format?: FormatOptions): { content: [{ type: 'text', text: string }] } { if (!format) { return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } switch (format.type) { case 'json': return this.formatJson(results, format.structure); case 'csv': return this.formatCsv(results, format.csv, format.structure); case 'sql': return this.formatSql(results, format.sql, format.structure); case 'xml': return this.formatXml(results, format.structure); default: return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } } private formatJson(results: any[], structure?: FormatOptions['structure']): { content: [{ type: 'text', text: string }] } { let formatted = results; if (structure) { formatted = results.map(result => { const formatted: any = {}; if (structure.flattenObjects) { this.flattenObject(result, formatted); } else { Object.assign(formatted, result); } if (structure.nameFormat) { this.formatName(formatted, structure.nameFormat); } if (structure.dateFormat) { this.formatDates(formatted, structure.dateFormat); } return formatted; }); } return { content: [ { type: 'text', text: JSON.stringify(formatted, null, 2) } ] }; } private formatCsv( results: any[], options?: FormatOptions['csv'], structure?: FormatOptions['structure'] ): { content: [{ type: 'text', text: string }] } { const delimiter = options?.delimiter || ','; const includeHeader = options?.includeHeader ?? true; const flattened = results.map(result => { const flat: any = {}; this.flattenObject(result, flat); return flat; }); const headers = Object.keys(flattened[0]); const rows = flattened.map(item => headers.map(header => item[header] ?? '').join(delimiter) ); if (includeHeader) { rows.unshift(headers.join(delimiter)); } return { content: [ { type: 'text', text: rows.join('\n') } ] }; } private formatSql( results: any[], options?: FormatOptions['sql'], structure?: FormatOptions['structure'] ): { content: [{ type: 'text', text: string }] } { const tableName = options?.tableName || 'users'; const dialect = options?.dialect || 'postgresql'; const includeCreate = options?.includeCreate ?? true; const flattened = results.map(result => { const flat: any = {}; this.flattenObject(result, flat); return flat; }); const columns = Object.keys(flattened[0]); let sql = ''; if (includeCreate) { sql += `CREATE TABLE IF NOT EXISTS ${tableName} (\n`; sql += ` id SERIAL PRIMARY KEY,\n`; sql += columns .map(col => ` ${col} VARCHAR(255)`) .join(',\n'); sql += '\n);\n\n'; } sql += `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES\n`; sql += flattened .map(item => `(${columns .map(col => `'${item[col]?.replace(/'/g, "''")}'`) .join(', ')})` ) .join(',\n'); sql += ';'; return { content: [ { type: 'text', text: sql } ] }; } private formatXml(results: any[], structure?: FormatOptions['structure']): { content: [{ type: 'text', text: string }] } { const formatValue = (value: any): string => { if (typeof value === 'object' && value !== null) { return Object.entries(value) .map(([k, v]) => `<${k}>${formatValue(v)}</${k}>`) .join(''); } return String(value); }; let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<users>\n'; results.forEach(result => { xml += ' <user>\n'; Object.entries(result).forEach(([key, value]) => { xml += ` <${key}>${formatValue(value)}</${key}>\n`; }); xml += ' </user>\n'; }); xml += '</users>'; return { content: [ { type: 'text', text: xml } ] }; } private flattenObject(obj: any, flat: any = {}, prefix = '') { Object.entries(obj).forEach(([key, value]) => { const newKey = prefix ? `${prefix}_${key}` : key; if (typeof value === 'object' && value !== null) { this.flattenObject(value, flat, newKey); } else { flat[newKey] = value; } }); return flat; } private formatName(obj: any, format: string) { if (!obj.name) return; switch (format) { case 'full': obj.name = `${obj.name.first} ${obj.name.last}`; break; case 'first_last': obj.first_name = obj.name.first; obj.last_name = obj.name.last; delete obj.name; break; // 'separate' is default format from API } } private formatDates(obj: any, format: string) { const formatDate = (date: string | number) => { const d = new Date(date); switch (format) { case 'unix': return Math.floor(d.getTime() / 1000); case 'iso': return d.toISOString(); case 'formatted': return d.toLocaleDateString(); default: return date; } }; if (obj.dob?.date) obj.dob.date = formatDate(obj.dob.date); if (obj.registered?.date) obj.registered.date = formatDate(obj.registered.date); } private async handleGetRandomUser(args: any) { try { const params: any = {}; if (args.gender) params.gender = args.gender; if (args.nationality) params.nat = args.nationality; if (args.fields?.mode === 'include') { params.inc = args.fields.values.join(','); } else if (args.fields?.mode === 'exclude') { params.exc = args.fields.values.join(','); } if (args.password) { const charsets = args.password.charsets?.join(',') || 'upper,lower,number'; params.password = args.password.maxLength ? `${charsets},${args.password.minLength || 8}-${args.password.maxLength}` : `${charsets},${args.password.minLength || 8}`; } const response = await this.axiosInstance.get('', { params }); return this.formatResults(response.data.results, args.format); } catch (error) { if (axios.isAxiosError(error)) { throw new McpError( ErrorCode.InternalError, `API Error: ${error.response?.data.error || error.message}` ); } throw error; } } private calculateCountForNationalityAndGender( args: any, nationality: string, gender: string ): number { if (!args.nationality) return 0; if (args.gender && args.gender !== gender) return 0; const totalCount = args.count || 0; const weights = args.nationalityWeights || {}; if (!Array.isArray(args.nationality)) { return nationality === args.nationality ? totalCount : 0; } if (!args.nationality.includes(nationality)) return 0; const natWeight = weights[nationality] || (1 / args.nationality.length); const genderRatio = args.gender ? 1 : 0.5; // If gender specified use full count, else split 50/50 return Math.round(totalCount * natWeight * genderRatio); } private async handleGetMultipleUsers(args: any) { try { const results = []; const params: any = {}; // Add field parameters this.addFieldParams(params, args); if (args.password) { const charsets = args.password.charsets?.join(',') || 'upper,lower,number'; params.password = args.password.maxLength ? `${charsets},${args.password.minLength || 8}-${args.password.maxLength}` : `${charsets},${args.password.minLength || 8}`; } // Make one API call per requested nationality+gender combination for (const gender of ['female', 'male']) { if (args.gender && args.gender !== gender) continue; params.gender = gender; // Handle single nationality or array of nationalities const nationalities = Array.isArray(args.nationality) ? args.nationality : [args.nationality]; for (const nat of nationalities) { params.nat = nat; const count = this.calculateCountForNationalityAndGender(args, nat, gender); if (count === 0) continue; params.results = count; const response = await this.axiosInstance.get('', { params }); results.push(...response.data.results); } } return this.formatResults(results, args.format); } catch (error) { if (axios.isAxiosError(error)) { throw new McpError( ErrorCode.InternalError, `API Error: ${error.response?.data.error || error.message}` ); } throw error; } } private addFieldParams(params: any, args: any): void { if (args.fields?.mode === 'include') { params.inc = args.fields.values.join(','); } else if (args.fields?.mode === 'exclude') { params.exc = args.fields.values.join(','); } } private setupErrorHandling() { this.server.onerror = (error: Error) => { console.error('[MCP Error]', error); if (error instanceof McpError) { switch (error.code) { case ErrorCode.InvalidParams: console.error('Invalid parameters provided:', error.message); break; case ErrorCode.MethodNotFound: console.error('The requested operation is not supported:', error.message); break; case ErrorCode.InternalError: console.error('An internal server error occurred:', error.message); break; default: console.error('An unexpected error occurred:', error.message); } } }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Random User MCP server running on stdio'); } } const server = new RandomUserServer(); server.run().catch(console.error);

Implementation Reference

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/rycid/randomuserMCP'

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