Skip to main content
Glama
slate-client.ts11.2 kB
/** * Slate API Client * Handles authentication and data retrieval from Technolutions Slate */ import { SlateConfig, StudentRecord, SlateQueryResponse, SlateQueryParams, Demographics, AcademicInfo, GeographicInfo, ApplicationStatus, AdmitType, EnrollmentStatus, } from './types.js'; export class SlateClient { private config: SlateConfig; private authHeader: string; constructor(config: SlateConfig) { this.config = config; // Slate typically uses Basic Auth this.authHeader = 'Basic ' + Buffer.from( `${config.username}:${config.password}` ).toString('base64'); } /** * Make an authenticated request to the Slate API */ private async request<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const url = `${this.config.baseUrl}${endpoint}`; const headers: Record<string, string> = { 'Authorization': this.authHeader, 'Content-Type': 'application/json', 'Accept': 'application/json', ...((options.headers as Record<string, string>) || {}), }; // If API key is provided, use it instead if (this.config.apiKey) { headers['X-API-Key'] = this.config.apiKey; } const response = await fetch(url, { ...options, headers, }); if (!response.ok) { const errorText = await response.text(); throw new Error( `Slate API error: ${response.status} ${response.statusText} - ${errorText}` ); } return response.json() as Promise<T>; } /** * Execute a Slate query to retrieve data * Slate queries are typically defined in the Slate admin panel and exposed via API */ async executeQuery<T>(params: SlateQueryParams): Promise<SlateQueryResponse<T>> { const queryParams = new URLSearchParams({ cmd: 'query', q: params.query, format: params.format || 'json', }); if (params.page) { queryParams.append('page', params.page.toString()); } if (params.pageSize) { queryParams.append('pageSize', params.pageSize.toString()); } if (params.filters) { Object.entries(params.filters).forEach(([key, value]) => { queryParams.append(key, String(value)); }); } return this.request<SlateQueryResponse<T>>( `/manage/query/run?${queryParams.toString()}` ); } /** * Get enrolled students for a specific entry year and term */ async getEnrolledStudents( entryYear: number, entryTerm?: string ): Promise<StudentRecord[]> { // This query name should match what's configured in your Slate instance // Common query names: enrolled_students, enrollment_census, etc. const queryName = 'enrollment_demographics'; const filters: Record<string, string | number> = { entry_year: entryYear, }; if (entryTerm) { filters.entry_term = entryTerm; } const response = await this.executeQuery<RawSlateStudent>({ query: queryName, filters, }); return response.row.map(this.mapToStudentRecord); } /** * Get all students by application status */ async getStudentsByStatus( status: ApplicationStatus, entryYear?: number ): Promise<StudentRecord[]> { const queryName = 'students_by_status'; const filters: Record<string, string | number> = { application_status: status, }; if (entryYear) { filters.entry_year = entryYear; } const response = await this.executeQuery<RawSlateStudent>({ query: queryName, filters, }); return response.row.map(this.mapToStudentRecord); } /** * Get funnel data (prospects -> applicants -> admitted -> enrolled) */ async getFunnelData(entryYear: number, entryTerm?: string): Promise<FunnelData> { const queryName = 'enrollment_funnel'; const filters: Record<string, string | number> = { entry_year: entryYear, }; if (entryTerm) { filters.entry_term = entryTerm; } const response = await this.executeQuery<RawFunnelData>({ query: queryName, filters, }); // Aggregate funnel data const funnel: FunnelData = { entryYear, entryTerm: entryTerm || 'All', prospects: 0, inquiries: 0, applicants: 0, admitted: 0, deposited: 0, enrolled: 0, }; for (const row of response.row) { funnel.prospects += row.prospects || 0; funnel.inquiries += row.inquiries || 0; funnel.applicants += row.applicants || 0; funnel.admitted += row.admitted || 0; funnel.deposited += row.deposited || 0; funnel.enrolled += row.enrolled || 0; } return funnel; } /** * Map raw Slate response to our StudentRecord type */ private mapToStudentRecord(raw: RawSlateStudent): StudentRecord { const demographics: Demographics = { gender: raw.gender || raw.sex, ethnicity: raw.ethnicity || raw.ethnic_code, race: raw.race ? (Array.isArray(raw.race) ? raw.race : [raw.race]) : undefined, citizenship: raw.citizenship || raw.citizen, firstGeneration: parseBoolean(raw.first_gen || raw.first_generation), legacyStatus: parseBoolean(raw.legacy || raw.legacy_status), veteranStatus: parseBoolean(raw.veteran || raw.veteran_status), dateOfBirth: raw.birth_date || raw.dob, }; const academicInfo: AcademicInfo = { intendedMajor: raw.major || raw.intended_major || raw.major_1, intendedCollege: raw.college || raw.intended_college, highSchoolGPA: parseFloat(raw.hs_gpa || raw.high_school_gpa) || undefined, transferGPA: parseFloat(raw.transfer_gpa) || undefined, testScores: { satTotal: parseInt(raw.sat_total || raw.sat_composite) || undefined, satMath: parseInt(raw.sat_math) || undefined, satVerbal: parseInt(raw.sat_verbal || raw.sat_ebrw) || undefined, actComposite: parseInt(raw.act_composite || raw.act) || undefined, }, highSchoolName: raw.hs_name || raw.high_school_name, highSchoolCity: raw.hs_city || raw.high_school_city, highSchoolState: raw.hs_state || raw.high_school_state, }; const geographicInfo: GeographicInfo = { city: raw.city || raw.home_city, state: raw.state || raw.home_state || raw.region, country: raw.country || raw.home_country || 'USA', zipCode: raw.zip || raw.postal_code || raw.zip_code, region: raw.geo_region || raw.territory, isInternational: (raw.country && raw.country !== 'USA' && raw.country !== 'United States') || parseBoolean(raw.international) || false, }; return { id: raw.id || raw.ref || raw.guid, firstName: raw.first || raw.first_name || raw.fname, lastName: raw.last || raw.last_name || raw.lname, email: raw.email || raw.email_address, applicationStatus: mapApplicationStatus(raw.app_status || raw.application_status || raw.status), admitType: mapAdmitType(raw.admit_type || raw.student_type || raw.level), enrollmentStatus: mapEnrollmentStatus(raw.enrollment_status || raw.enroll_status), entryTerm: raw.entry_term || raw.term || raw.admit_term, entryYear: parseInt(raw.entry_year || raw.year || raw.admit_year) || new Date().getFullYear(), demographics, academicInfo, geographicInfo, }; } } // Raw Slate data types (field names vary by institution) interface RawSlateStudent { id?: string; ref?: string; guid?: string; first?: string; first_name?: string; fname?: string; last?: string; last_name?: string; lname?: string; email?: string; email_address?: string; gender?: string; sex?: string; ethnicity?: string; ethnic_code?: string; race?: string | string[]; citizenship?: string; citizen?: string; first_gen?: string; first_generation?: string; legacy?: string; legacy_status?: string; veteran?: string; veteran_status?: string; birth_date?: string; dob?: string; major?: string; intended_major?: string; major_1?: string; college?: string; intended_college?: string; hs_gpa?: string; high_school_gpa?: string; transfer_gpa?: string; sat_total?: string; sat_composite?: string; sat_math?: string; sat_verbal?: string; sat_ebrw?: string; act_composite?: string; act?: string; hs_name?: string; high_school_name?: string; hs_city?: string; high_school_city?: string; hs_state?: string; high_school_state?: string; city?: string; home_city?: string; state?: string; home_state?: string; region?: string; country?: string; home_country?: string; zip?: string; postal_code?: string; zip_code?: string; geo_region?: string; territory?: string; international?: string; app_status?: string; application_status?: string; status?: string; admit_type?: string; student_type?: string; level?: string; enrollment_status?: string; enroll_status?: string; entry_term?: string; term?: string; admit_term?: string; entry_year?: string; year?: string; admit_year?: string; } interface RawFunnelData { prospects?: number; inquiries?: number; applicants?: number; admitted?: number; deposited?: number; enrolled?: number; } export interface FunnelData { entryYear: number; entryTerm: string; prospects: number; inquiries: number; applicants: number; admitted: number; deposited: number; enrolled: number; } // Helper functions function parseBoolean(value: string | boolean | undefined): boolean | undefined { if (value === undefined || value === null || value === '') return undefined; if (typeof value === 'boolean') return value; const lower = value.toLowerCase(); return lower === 'true' || lower === 'yes' || lower === 'y' || lower === '1'; } function mapApplicationStatus(status: string | undefined): ApplicationStatus { if (!status) return 'prospect'; const lower = status.toLowerCase(); if (lower.includes('admit') || lower.includes('accepted')) return 'admitted'; if (lower.includes('deny') || lower.includes('denied') || lower.includes('reject')) return 'denied'; if (lower.includes('wait')) return 'waitlisted'; if (lower.includes('withdraw')) return 'withdrawn'; if (lower.includes('appl') || lower.includes('complete')) return 'applicant'; if (lower.includes('inquir')) return 'inquiry'; return 'prospect'; } function mapAdmitType(type: string | undefined): AdmitType { if (!type) return 'freshman'; const lower = type.toLowerCase(); if (lower.includes('transfer') || lower.includes('tr')) return 'transfer'; if (lower.includes('grad')) return 'graduate'; if (lower.includes('readmit')) return 'readmit'; if (lower.includes('non') && lower.includes('degree')) return 'non-degree'; return 'freshman'; } function mapEnrollmentStatus(status: string | undefined): EnrollmentStatus { if (!status) return 'not_enrolled'; const lower = status.toLowerCase(); if (lower.includes('enroll') && !lower.includes('not')) return 'enrolled'; if (lower.includes('deposit')) return 'deposited'; if (lower.includes('defer')) return 'deferred'; if (lower.includes('withdraw')) return 'withdrawn'; return 'not_enrolled'; }

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/pwfarmer87/Slate-MCP'

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