okta-mcp-server
Version:
Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching
240 lines • 7.78 kB
JavaScript
import { z } from 'zod';
import { CSVUserInputSchema, JSONUserInputSchema } from './schemas.js';
/**
* Parse CSV data into user objects
*/
export function parseCSV(csvData, mapping) {
const lines = csvData.trim().split('\n');
if (lines.length < 2) {
throw new Error('CSV must contain at least a header row and one data row');
}
// Parse header
const headers = lines[0].split(',').map((h) => h.trim().replace(/^"|"$/g, ''));
// Create reverse mapping if provided
const fieldMapping = {};
if (mapping) {
// Standard fields
if (mapping['email'])
fieldMapping[mapping['email']] = 'email';
if (mapping['firstName'])
fieldMapping[mapping['firstName']] = 'firstName';
if (mapping['lastName'])
fieldMapping[mapping['lastName']] = 'lastName';
if (mapping['login'])
fieldMapping[mapping['login']] = 'login';
if (mapping['mobilePhone'])
fieldMapping[mapping['mobilePhone']] = 'mobilePhone';
if (mapping['department'])
fieldMapping[mapping['department']] = 'department';
if (mapping['title'])
fieldMapping[mapping['title']] = 'title';
if (mapping['manager'])
fieldMapping[mapping['manager']] = 'manager';
// Custom attributes
if (mapping['customAttributes']) {
Object.entries(mapping['customAttributes']).forEach(([oktaField, csvColumn]) => {
fieldMapping[csvColumn] = oktaField;
});
}
}
// Parse data rows
const users = [];
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line)
continue;
const values = parseCSVLine(line);
const user = {};
headers.forEach((header, index) => {
const value = values[index];
if (index < values.length && values?.[index] !== undefined && values[index] !== '') {
const fieldName = fieldMapping[header] || header;
user[fieldName] = value;
}
});
users.push(user);
}
return users;
}
/**
* Parse a single CSV line, handling quoted values
*/
function parseCSVLine(line) {
const values = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
const nextChar = line[i + 1];
if (char === '"') {
if (inQuotes && nextChar === '"') {
// Escaped quote
current += '"';
i++; // Skip next quote
}
else {
// Toggle quote state
inQuotes = !inQuotes;
}
}
else if (char === ',' && !inQuotes) {
// End of field
values.push(current.trim());
current = '';
}
else {
current += char;
}
}
// Add last field
values.push(current.trim());
return values;
}
/**
* Convert user objects to CSV format
*/
export function toCSV(users, fields) {
// Create header
const headers = fields.map((field) => {
// Handle nested fields
if (field.includes('.')) {
return field.split('.').pop();
}
return field;
});
const csvLines = [headers.map((h) => `"${h}"`).join(',')];
// Add data rows
for (const user of users) {
const values = fields.map((field) => {
let value = user;
// Handle nested fields
const parts = field.split('.');
for (const part of parts) {
value = value?.[part];
}
// Format value
if (value === null || value === undefined) {
return '';
}
if (typeof value === 'object') {
return `"${JSON.stringify(value).replace(/"/g, '""')}"`;
}
const strValue = String(value);
if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) {
return `"${strValue.replace(/"/g, '""')}"`;
}
return String(value);
});
csvLines.push(values.join(','));
}
return csvLines.join('\n');
}
/**
* Validate and transform CSV user data to Okta format
*/
export function validateCSVUsers(users) {
const validatedUsers = [];
const errors = [];
users.forEach((user, index) => {
try {
// Validate required fields
const validated = CSVUserInputSchema.parse(user);
// Transform to Okta format
const oktaUser = {
profile: {
...validated,
login: validated.login || validated.email,
},
};
validatedUsers.push(oktaUser);
}
catch (error) {
if (error instanceof z.ZodError) {
errors.push({
index,
error: `Row ${index + 1}: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
});
}
else {
errors.push({
index,
error: `Row ${index + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`,
});
}
}
});
if (errors.length > 0) {
const errorMessage = errors.map((e) => e.error).join('\n');
throw new Error(`Validation errors:\n${errorMessage}`);
}
return validatedUsers;
}
/**
* Validate JSON user data
*/
export function validateJSONUsers(data) {
let parsed;
try {
parsed = JSON.parse(data);
}
catch (error) {
throw new Error('Invalid JSON format');
}
if (!Array.isArray(parsed)) {
throw new Error('JSON data must be an array of user objects');
}
const validatedUsers = [];
const errors = [];
parsed.forEach((user, index) => {
try {
const validated = JSONUserInputSchema.parse(user);
// Ensure login is set
if (!validated.profile.login) {
validated.profile.login = validated.profile.email;
}
validatedUsers.push(validated);
}
catch (error) {
if (error instanceof z.ZodError) {
errors.push({
index,
error: `User ${index + 1}: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
});
}
else {
errors.push({
index,
error: `User ${index + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`,
});
}
}
});
if (errors.length > 0) {
const errorMessage = errors.map((e) => e.error).join('\n');
throw new Error(`Validation errors:\n${errorMessage}`);
}
return validatedUsers;
}
/**
* Generate sample data for import templates
*/
export function generateSampleUsers(count, includeOptional) {
const samples = [];
for (let i = 1; i <= count; i++) {
const sample = {
email: `user${i}@example.com`,
firstName: `First${i}`,
lastName: `Last${i}`,
};
if (includeOptional) {
sample['login'] = `user${i}`;
sample['mobilePhone'] = `+1-555-${String(i).padStart(4, '0')}`;
sample['department'] = `Department ${i}`;
sample['title'] = `Title ${i}`;
sample['manager'] = `manager${i}@example.com`;
}
samples.push(sample);
}
return samples;
}
//# sourceMappingURL=parsers.js.map