upload_csv
Process employee data from a CSV file to enable Solana token airdrops, role-based allocations, and automated email notifications for corporate HR teams.
Instructions
Process employee data from a CSV file
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filePath | Yes | Path to the CSV file |
Implementation Reference
- src/server.ts:221-234 (registration)Registers the 'upload_csv' tool in the ListToolsRequestSchema handler, including name, description, and inputSchema for filePath.{ name: 'upload_csv', description: 'Process employee data from a CSV file', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the CSV file', }, }, required: ['filePath'], }, },
- src/server.ts:326-327 (registration)Dispatches 'upload_csv' tool calls to the handleUploadCsv method in the CallToolRequestSchema handler.case 'upload_csv': return await this.handleUploadCsv(args);
- src/server.ts:770-862 (handler)The primary handler for the 'upload_csv' tool. Validates input, reads and parses CSV using csv-parse/sync, matches records to existing employee wallets by email, updates names and roles, validates roles, updates server state, and returns role distribution summary.private async handleUploadCsv(args: any) { // Validate input const schema = z.object({ filePath: z.string(), }); const { filePath } = schema.parse(args); try { // Read and parse CSV file const fileContent = fs.readFileSync(filePath, 'utf8'); const records = csvParse(fileContent, { columns: true, skip_empty_lines: true, }); // Validate records const validRoles = ['operational', 'developer', 'manager', 'VP', 'VIP']; if (!records.length) { throw new McpError(ErrorCode.InvalidParams, 'CSV file is empty.'); } // Check required columns const firstRecord = records[0]; if (!('email' in firstRecord)) { throw new McpError(ErrorCode.InvalidParams, 'CSV file must have an "email" column.'); } // Map existing emails to records const employeesByEmail = new Map( this.state.employees.map((e) => [e.email, e]) ); // Update or create employee records const updatedEmployees = records.map((record: any) => { if (!record.email) { throw new McpError(ErrorCode.InvalidParams, 'Every row must have an email.'); } const existingEmployee = employeesByEmail.get(record.email); if (!existingEmployee) { throw new McpError( ErrorCode.InvalidParams, `Email ${record.email} does not match any generated wallet. Please generate wallets first.` ); } if (record.role && !validRoles.includes(record.role.toLowerCase())) { throw new McpError( ErrorCode.InvalidParams, `Invalid role "${record.role}" for ${record.email}. Valid roles are: ${validRoles.join(', ')}.` ); } return { ...existingEmployee, name: record.name || undefined, role: record.role?.toLowerCase() || undefined, }; }); // Update state this.state.employees = updatedEmployees; return { content: [ { type: 'text', text: ` CSV data processed successfully. Updated ${updatedEmployees.length} employee records. Role distribution: - Operational: ${updatedEmployees.filter((e: any) => e.role === 'operational').length} - Developer: ${updatedEmployees.filter((e: any) => e.role === 'developer').length} - Manager: ${updatedEmployees.filter((e: any) => e.role === 'manager').length} - VP: ${updatedEmployees.filter((e: any) => e.role === 'vp').length} - VIP: ${updatedEmployees.filter((e: any) => e.role === 'vip').length} - No role: ${updatedEmployees.filter((e: any) => !e.role).length} Next step: Calculate token amounts for each employee. `.trim(), }, ], }; } catch (error) { if (error instanceof McpError) throw error; throw new McpError( ErrorCode.InternalError, `Failed to process CSV: ${error instanceof Error ? error.message : String(error)}` ); } }
- src/server.ts:772-775 (schema)Zod schema for input validation within the handler (filePath: string).const schema = z.object({ filePath: z.string(), });