Skip to main content
Glama
waldzellai

Exa Websets MCP Server

by waldzellai
EnrichmentService.ts11.5 kB
/** * Enrichment Service * * Service for managing webset enrichments - creation, monitoring, and management. */ import { BaseService } from './BaseService.js'; import { WebsetEnrichment, CreateEnrichmentRequest, EnrichmentOption } from '../types/websets.js'; export class EnrichmentService extends BaseService { /** * Create a new enrichment for a webset */ async createEnrichment(request: CreateEnrichmentRequest): Promise<WebsetEnrichment> { this.validateRequired({ websetId: request.websetId, description: request.description }, ['websetId', 'description']); this.validateCreateEnrichmentRequest(request); this.logOperation('createEnrichment', { websetId: request.websetId, format: request.format }); const endpoint = this.buildEndpoint('/websets/{websetId}/enrichments', { websetId: request.websetId }); const sanitizedRequest = this.sanitizeParams({ description: request.description, format: request.format, options: request.options, metadata: request.metadata, }); return this.handlePostRequest<WebsetEnrichment>(endpoint, sanitizedRequest); } /** * Get an enrichment by ID */ async getEnrichment(websetId: string, enrichmentId: string): Promise<WebsetEnrichment> { this.validateRequired({ websetId, enrichmentId }, ['websetId', 'enrichmentId']); this.logOperation('getEnrichment', { websetId, enrichmentId }); const endpoint = this.buildEndpoint('/websets/{websetId}/enrichments/{enrichmentId}', { websetId, enrichmentId }); return this.handleGetRequest<WebsetEnrichment>(endpoint); } /** * Delete an enrichment */ async deleteEnrichment(websetId: string, enrichmentId: string): Promise<WebsetEnrichment> { this.validateRequired({ websetId, enrichmentId }, ['websetId', 'enrichmentId']); this.logOperation('deleteEnrichment', { websetId, enrichmentId }); const endpoint = this.buildEndpoint('/websets/{websetId}/enrichments/{enrichmentId}', { websetId, enrichmentId }); return this.handleDeleteRequest<WebsetEnrichment>(endpoint); } /** * Cancel a running enrichment */ async cancelEnrichment(websetId: string, enrichmentId: string): Promise<WebsetEnrichment> { this.validateRequired({ websetId, enrichmentId }, ['websetId', 'enrichmentId']); this.logOperation('cancelEnrichment', { websetId, enrichmentId }); const endpoint = this.buildEndpoint('/websets/{websetId}/enrichments/{enrichmentId}/cancel', { websetId, enrichmentId }); return this.handlePostRequest<WebsetEnrichment>(endpoint); } /** * Get enrichment status with polling support */ async getEnrichmentStatus(websetId: string, enrichmentId: string, pollUntilComplete: boolean = false): Promise<WebsetEnrichment> { this.validateRequired({ websetId, enrichmentId }, ['websetId', 'enrichmentId']); this.logOperation('getEnrichmentStatus', { websetId, enrichmentId, pollUntilComplete }); const endpoint = this.buildEndpoint('/websets/{websetId}/enrichments/{enrichmentId}', { websetId, enrichmentId }); if (!pollUntilComplete) { return this.handleGetRequest<WebsetEnrichment>(endpoint); } // Poll until enrichment is complete return this.pollForCompletion<WebsetEnrichment>( endpoint, (enrichment) => this.isEnrichmentComplete(enrichment), 120, // max 120 attempts (10 minutes at 5s intervals) 5000 // check every 5 seconds ); } /** * Wait for enrichment to complete with timeout */ async waitForEnrichmentCompletion(websetId: string, enrichmentId: string, timeoutMs: number = 600000): Promise<WebsetEnrichment> { this.validateRequired({ websetId, enrichmentId }, ['websetId', 'enrichmentId']); this.logOperation('waitForEnrichmentCompletion', { websetId, enrichmentId, timeoutMs }); const startTime = Date.now(); const maxAttempts = Math.ceil(timeoutMs / 5000); return this.pollForCompletion<WebsetEnrichment>( this.buildEndpoint('/websets/{websetId}/enrichments/{enrichmentId}', { websetId, enrichmentId }), (enrichment) => { const elapsed = Date.now() - startTime; if (elapsed >= timeoutMs) { throw new Error(`Enrichment completion timeout after ${timeoutMs}ms`); } return this.isEnrichmentComplete(enrichment); }, maxAttempts, 5000 ); } /** * Check if enrichment is complete */ isEnrichmentComplete(enrichment: WebsetEnrichment): boolean { return enrichment.status === 'completed' || enrichment.status === 'canceled'; } /** * Check if enrichment is running */ isEnrichmentRunning(enrichment: WebsetEnrichment): boolean { return enrichment.status === 'pending'; } /** * Check if enrichment was canceled */ isEnrichmentCanceled(enrichment: WebsetEnrichment): boolean { return enrichment.status === 'canceled'; } /** * Create a text enrichment */ async createTextEnrichment( websetId: string, description: string, metadata?: Record<string, string> ): Promise<WebsetEnrichment> { return this.createEnrichment({ websetId, description, format: 'text', metadata, }); } /** * Create a date enrichment */ async createDateEnrichment( websetId: string, description: string, metadata?: Record<string, string> ): Promise<WebsetEnrichment> { return this.createEnrichment({ websetId, description, format: 'date', metadata, }); } /** * Create a number enrichment */ async createNumberEnrichment( websetId: string, description: string, metadata?: Record<string, string> ): Promise<WebsetEnrichment> { return this.createEnrichment({ websetId, description, format: 'number', metadata, }); } /** * Create an email enrichment */ async createEmailEnrichment( websetId: string, description: string, metadata?: Record<string, string> ): Promise<WebsetEnrichment> { return this.createEnrichment({ websetId, description, format: 'email', metadata, }); } /** * Create a phone enrichment */ async createPhoneEnrichment( websetId: string, description: string, metadata?: Record<string, string> ): Promise<WebsetEnrichment> { return this.createEnrichment({ websetId, description, format: 'phone', metadata, }); } /** * Create an options enrichment */ async createOptionsEnrichment( websetId: string, description: string, options: string[], metadata?: Record<string, string> ): Promise<WebsetEnrichment> { const enrichmentOptions: EnrichmentOption[] = options.map(label => ({ label })); return this.createEnrichment({ websetId, description, format: 'options', options: enrichmentOptions, metadata, }); } /** * Create multiple enrichments at once */ async createBulkEnrichments( websetId: string, enrichments: Array<{ description: string; format: string; options?: string[]; metadata?: Record<string, string>; }> ): Promise<WebsetEnrichment[]> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('createBulkEnrichments', { websetId, count: enrichments.length }); const results: WebsetEnrichment[] = []; for (const enrichment of enrichments) { const options = enrichment.options?.map(label => ({ label })); const result = await this.createEnrichment({ websetId, description: enrichment.description, format: enrichment.format as any, options, metadata: enrichment.metadata, }); results.push(result); } return results; } /** * Get all enrichments for a webset */ async getAllEnrichments(websetId: string): Promise<WebsetEnrichment[]> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('getAllEnrichments', { websetId }); // Note: This assumes the webset object includes enrichments // In practice, you might need a separate API endpoint const webset = await this.handleGetRequest<any>(`/websets/${websetId}?expand=enrichments`); return webset.enrichments || []; } /** * Get enrichment statistics for a webset */ async getEnrichmentStats(websetId: string): Promise<{ total: number; pending: number; completed: number; canceled: number; byFormat: Record<string, number>; }> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('getEnrichmentStats', { websetId }); const enrichments = await this.getAllEnrichments(websetId); const stats = { total: enrichments.length, pending: 0, completed: 0, canceled: 0, byFormat: {} as Record<string, number>, }; for (const enrichment of enrichments) { // Count by status switch (enrichment.status) { case 'pending': stats.pending++; break; case 'completed': stats.completed++; break; case 'canceled': stats.canceled++; break; } // Count by format const format = enrichment.format; stats.byFormat[format] = (stats.byFormat[format] || 0) + 1; } return stats; } /** * Validate enrichment creation request */ private validateCreateEnrichmentRequest(request: CreateEnrichmentRequest): void { // Validate description if (!request.description || typeof request.description !== 'string' || request.description.trim().length === 0) { throw new Error('Description must be a non-empty string'); } if (request.description.length > 5000) { throw new Error('Description must be less than 5000 characters'); } // Validate format const validFormats = ['text', 'date', 'number', 'options', 'email', 'phone']; if (!validFormats.includes(request.format)) { throw new Error(`Format must be one of: ${validFormats.join(', ')}`); } // Validate options for options format if (request.format === 'options') { if (!request.options || !Array.isArray(request.options) || request.options.length === 0) { throw new Error('Options are required for options format'); } for (const option of request.options) { if (!option.label || typeof option.label !== 'string' || option.label.trim().length === 0) { throw new Error('Each option must have a non-empty label'); } if (option.label.length > 100) { throw new Error('Option labels must be less than 100 characters'); } } if (request.options.length > 50) { throw new Error('Maximum 50 options allowed'); } } // Validate metadata if (request.metadata) { if (typeof request.metadata !== 'object') { throw new Error('Metadata must be an object'); } for (const [key, value] of Object.entries(request.metadata)) { if (typeof value !== 'string') { throw new Error(`Metadata value for key '${key}' must be a string`); } if (value.length > 1000) { throw new Error(`Metadata value for key '${key}' must be less than 1000 characters`); } } } } }

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/waldzellai/exa-mcp-server-websets'

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