Skip to main content
Glama
waldzellai

Exa Websets MCP Server

by waldzellai
ItemService.ts9.2 kB
/** * Item Service * * Service for managing webset items - retrieval, filtering, and analysis. */ import { BaseService } from './BaseService.js'; import { WebsetItem, PaginatedResponse, ItemEntity, ItemVerification } from '../types/websets.js'; export class ItemService extends BaseService { /** * Get a specific item by ID */ async getItem(websetId: string, itemId: string): Promise<WebsetItem> { this.validateRequired({ websetId, itemId }, ['websetId', 'itemId']); this.logOperation('getItem', { websetId, itemId }); const endpoint = this.buildEndpoint('/websets/{websetId}/items/{itemId}', { websetId, itemId }); return this.handleGetRequest<WebsetItem>(endpoint); } /** * List items for a webset with pagination */ async listItems( websetId: string, cursor?: string, limit?: number, batchSize?: number ): Promise<PaginatedResponse<WebsetItem>> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('listItems', { websetId, cursor, limit, batchSize }); const endpoint = this.buildEndpoint('/websets/{websetId}/items', { websetId }); const params: Record<string, any> = {}; if (batchSize) { params.batchSize = batchSize; } return this.handlePaginatedRequest<WebsetItem>(endpoint, params, cursor, limit); } /** * Delete an item from a webset */ async deleteItem(websetId: string, itemId: string): Promise<WebsetItem> { this.validateRequired({ websetId, itemId }, ['websetId', 'itemId']); this.logOperation('deleteItem', { websetId, itemId }); const endpoint = this.buildEndpoint('/websets/{websetId}/items/{itemId}', { websetId, itemId }); return this.handleDeleteRequest<WebsetItem>(endpoint); } /** * Get all items for a webset (handles pagination automatically) */ async getAllItems(websetId: string, batchSize: number = 50): Promise<WebsetItem[]> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('getAllItems', { websetId, batchSize }); const allItems: WebsetItem[] = []; let cursor: string | undefined; let hasMore = true; while (hasMore) { const response = await this.listItems(websetId, cursor, undefined, batchSize); allItems.push(...response.data); hasMore = response.hasMore; cursor = response.nextCursor; } return allItems; } /** * Filter items by verification status */ async getItemsByVerificationStatus( websetId: string, status: 'verified' | 'unverified' | 'rejected' ): Promise<WebsetItem[]> { this.validateRequired({ websetId, status }, ['websetId', 'status']); this.logOperation('getItemsByVerificationStatus', { websetId, status }); const allItems = await this.getAllItems(websetId); return allItems.filter(item => item.verification.status === status); } /** * Filter items by entity type */ async getItemsByEntityType(websetId: string, entityType: string): Promise<WebsetItem[]> { this.validateRequired({ websetId, entityType }, ['websetId', 'entityType']); this.logOperation('getItemsByEntityType', { websetId, entityType }); const allItems = await this.getAllItems(websetId); return allItems.filter(item => item.entity.type === entityType); } /** * Search items by content */ async searchItemsByContent(websetId: string, searchTerm: string): Promise<WebsetItem[]> { this.validateRequired({ websetId, searchTerm }, ['websetId', 'searchTerm']); this.logOperation('searchItemsByContent', { websetId, searchTerm }); const allItems = await this.getAllItems(websetId); const lowerSearchTerm = searchTerm.toLowerCase(); return allItems.filter(item => item.title.toLowerCase().includes(lowerSearchTerm) || item.content.toLowerCase().includes(lowerSearchTerm) ); } /** * Get items with enrichments */ async getEnrichedItems(websetId: string): Promise<WebsetItem[]> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('getEnrichedItems', { websetId }); const allItems = await this.getAllItems(websetId); return allItems.filter(item => item.enrichments && Object.keys(item.enrichments).length > 0 ); } /** * Get items by search ID */ async getItemsBySearchId(websetId: string, searchId: string): Promise<WebsetItem[]> { this.validateRequired({ websetId, searchId }, ['websetId', 'searchId']); this.logOperation('getItemsBySearchId', { websetId, searchId }); const allItems = await this.getAllItems(websetId); return allItems.filter(item => item.searchId === searchId); } /** * Get item statistics for a webset */ async getItemStats(websetId: string): Promise<{ total: number; verified: number; unverified: number; rejected: number; enriched: number; byEntityType: Record<string, number>; bySearchId: Record<string, number>; }> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('getItemStats', { websetId }); const allItems = await this.getAllItems(websetId); const stats = { total: allItems.length, verified: 0, unverified: 0, rejected: 0, enriched: 0, byEntityType: {} as Record<string, number>, bySearchId: {} as Record<string, number>, }; for (const item of allItems) { // Count by verification status switch (item.verification.status) { case 'verified': stats.verified++; break; case 'unverified': stats.unverified++; break; case 'rejected': stats.rejected++; break; } // Count enriched items if (item.enrichments && Object.keys(item.enrichments).length > 0) { stats.enriched++; } // Count by entity type const entityType = item.entity.type; stats.byEntityType[entityType] = (stats.byEntityType[entityType] || 0) + 1; // Count by search ID const searchId = item.searchId; stats.bySearchId[searchId] = (stats.bySearchId[searchId] || 0) + 1; } return stats; } /** * Export items to JSON */ async exportItemsToJson(websetId: string): Promise<string> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('exportItemsToJson', { websetId }); const allItems = await this.getAllItems(websetId); return JSON.stringify(allItems, null, 2); } /** * Export items to CSV format */ async exportItemsToCsv(websetId: string): Promise<string> { this.validateRequired({ websetId }, ['websetId']); this.logOperation('exportItemsToCsv', { websetId }); const allItems = await this.getAllItems(websetId); if (allItems.length === 0) { return 'No items found'; } // CSV headers const headers = [ 'id', 'title', 'url', 'entity_type', 'verification_status', 'verification_reasoning', 'search_id', 'created_at', 'updated_at' ]; // Add enrichment columns const enrichmentKeys = new Set<string>(); for (const item of allItems) { if (item.enrichments) { Object.keys(item.enrichments).forEach(key => enrichmentKeys.add(key)); } } headers.push(...Array.from(enrichmentKeys).map(key => `enrichment_${key}`)); // Build CSV rows const rows = [headers.join(',')]; for (const item of allItems) { const row = [ this.escapeCsvValue(item.id), this.escapeCsvValue(item.title), this.escapeCsvValue(item.url), this.escapeCsvValue(item.entity.type), this.escapeCsvValue(item.verification.status), this.escapeCsvValue(item.verification.reasoning), this.escapeCsvValue(item.searchId), this.escapeCsvValue(item.createdAt), this.escapeCsvValue(item.updatedAt), ]; // Add enrichment values for (const key of enrichmentKeys) { const value = item.enrichments?.[key]; row.push(this.escapeCsvValue(value ? String(value) : '')); } rows.push(row.join(',')); } return rows.join('\n'); } /** * Check if item is verified */ isItemVerified(item: WebsetItem): boolean { return item.verification.status === 'verified'; } /** * Check if item is enriched */ isItemEnriched(item: WebsetItem): boolean { return item.enrichments && Object.keys(item.enrichments).length > 0; } /** * Get item enrichment value */ getEnrichmentValue(item: WebsetItem, enrichmentKey: string): any { return item.enrichments?.[enrichmentKey]; } /** * Validate URL format */ validateItemUrl(url: string): boolean { return this.validateUrl(url); } /** * Escape CSV values */ private escapeCsvValue(value: string): string { if (value.includes(',') || value.includes('"') || value.includes('\n')) { return `"${value.replace(/"/g, '""')}"`; } return value; } }

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