UNPKG

marketing-post-generator-mcp

Version:

A powerful MCP server for AI-powered marketing blog post generation with Claude integration

293 lines (292 loc) 11.8 kB
import { createLogger } from '../../utils/logger.js'; /** * Main search service that provides a unified interface to search adapters * with fallback support and error handling. */ export class SearchService { logger; adapter; fallbackAdapters; constructor(primaryAdapter, fallbacks = [], logger) { this.logger = logger || createLogger({ level: 'info', format: 'simple' }); this.adapter = primaryAdapter; this.fallbackAdapters = fallbacks; this.logger.info('SearchService initialized', { primaryAdapter: primaryAdapter.getName(), fallbackCount: fallbacks.length, fallbackAdapters: fallbacks.map((adapter) => adapter.getName()), }); } /** * Set a new primary adapter * @param adapter The new primary adapter to use */ setAdapter(adapter) { const previousName = this.adapter.getName(); this.adapter = adapter; this.logger.info('Primary search adapter changed', { from: previousName, to: adapter.getName(), }); } /** * Add a fallback adapter * @param adapter The fallback adapter to add */ addFallbackAdapter(adapter) { this.fallbackAdapters.push(adapter); this.logger.info('Fallback adapter added', { adapter: adapter.getName(), totalFallbacks: this.fallbackAdapters.length, }); } /** * Get the current primary adapter name * @returns The name of the primary adapter */ getPrimaryAdapterName() { return this.adapter.getName(); } /** * Get the names of all fallback adapters * @returns Array of fallback adapter names */ getFallbackAdapterNames() { return this.fallbackAdapters.map((adapter) => adapter.getName()); } /** * Fetch content from a URL with fallback support * @param url The URL to fetch content from * @returns Promise resolving to the content */ async fetchContent(url) { const startTime = Date.now(); this.logger.info('Fetching content', { url, primaryAdapter: this.adapter.getName() }); try { const content = await this.adapter.fetchContent(url); const duration = Date.now() - startTime; this.logger.info('Content fetched successfully', { url, adapter: this.adapter.getName(), duration, contentLength: content.length, }); return content; } catch (error) { this.logger.warn('Primary adapter failed, trying fallbacks', { url, primaryAdapter: this.adapter.getName(), error: error instanceof Error ? error.message : String(error), fallbacksAvailable: this.fallbackAdapters.length, }); // Try fallback adapters for (const fallbackAdapter of this.fallbackAdapters) { try { this.logger.debug('Trying fallback adapter', { url, adapter: fallbackAdapter.getName(), }); const content = await fallbackAdapter.fetchContent(url); const duration = Date.now() - startTime; this.logger.info('Content fetched with fallback adapter', { url, adapter: fallbackAdapter.getName(), duration, contentLength: content.length, }); return content; } catch (fallbackError) { this.logger.debug('Fallback adapter failed', { url, adapter: fallbackAdapter.getName(), error: fallbackError instanceof Error ? fallbackError.message : String(fallbackError), }); } } const duration = Date.now() - startTime; this.logger.error('All adapters failed to fetch content', { url, duration, adaptersAttempted: 1 + this.fallbackAdapters.length, }); throw new Error(`Failed to fetch content from ${url} with all adapters. Primary: ${this.adapter.getName()}, Fallbacks: ${this.fallbackAdapters.map((a) => a.getName()).join(', ')}`); } } /** * Sample domain with fallback support * @param domain The domain to sample from * @param count Number of samples to collect * @returns Promise resolving to array of sampled content */ async sampleDomain(domain, count) { const startTime = Date.now(); this.logger.info('Sampling domain', { domain, count, primaryAdapter: this.adapter.getName(), }); try { const samples = await this.adapter.sampleDomain(domain, count); const duration = Date.now() - startTime; this.logger.info('Domain sampled successfully', { domain, adapter: this.adapter.getName(), requestedCount: count, actualCount: samples.length, duration, }); return samples; } catch (error) { this.logger.warn('Primary adapter failed, trying fallbacks', { domain, primaryAdapter: this.adapter.getName(), error: error instanceof Error ? error.message : String(error), fallbacksAvailable: this.fallbackAdapters.length, }); // Try fallback adapters for (const fallbackAdapter of this.fallbackAdapters) { try { this.logger.debug('Trying fallback adapter', { domain, adapter: fallbackAdapter.getName(), }); const samples = await fallbackAdapter.sampleDomain(domain, count); const duration = Date.now() - startTime; this.logger.info('Domain sampled with fallback adapter', { domain, adapter: fallbackAdapter.getName(), requestedCount: count, actualCount: samples.length, duration, }); return samples; } catch (fallbackError) { this.logger.debug('Fallback adapter failed', { domain, adapter: fallbackAdapter.getName(), error: fallbackError instanceof Error ? fallbackError.message : String(fallbackError), }); } } const duration = Date.now() - startTime; this.logger.error('All adapters failed to sample domain', { domain, duration, adaptersAttempted: 1 + this.fallbackAdapters.length, }); throw new Error(`Failed to sample domain ${domain} with all adapters. Primary: ${this.adapter.getName()}, Fallbacks: ${this.fallbackAdapters.map((a) => a.getName()).join(', ')}`); } } /** * Search with fallback support * @param query The search query * @param options Optional search parameters * @returns Promise resolving to search results */ async search(query, options) { const startTime = Date.now(); this.logger.info('Performing search', { query, options, primaryAdapter: this.adapter.getName(), }); try { const results = await this.adapter.search(query, options); const duration = Date.now() - startTime; this.logger.info('Search completed successfully', { query, adapter: this.adapter.getName(), resultCount: results.length, duration, }); return results; } catch (error) { this.logger.warn('Primary adapter failed, trying fallbacks', { query, primaryAdapter: this.adapter.getName(), error: error instanceof Error ? error.message : String(error), fallbacksAvailable: this.fallbackAdapters.length, }); // Try fallback adapters for (const fallbackAdapter of this.fallbackAdapters) { try { this.logger.debug('Trying fallback adapter', { query, adapter: fallbackAdapter.getName(), }); const results = await fallbackAdapter.search(query, options); const duration = Date.now() - startTime; this.logger.info('Search completed with fallback adapter', { query, adapter: fallbackAdapter.getName(), resultCount: results.length, duration, }); return results; } catch (fallbackError) { this.logger.debug('Fallback adapter failed', { query, adapter: fallbackAdapter.getName(), error: fallbackError instanceof Error ? fallbackError.message : String(fallbackError), }); } } const duration = Date.now() - startTime; this.logger.error('All adapters failed to perform search', { query, duration, adaptersAttempted: 1 + this.fallbackAdapters.length, }); throw new Error(`Failed to search for "${query}" with all adapters. Primary: ${this.adapter.getName()}, Fallbacks: ${this.fallbackAdapters.map((a) => a.getName()).join(', ')}`); } } /** * Check health of all adapters * @returns Promise resolving to health status of primary and fallback adapters */ async healthCheck() { this.logger.debug('Performing health check on all adapters'); // Check primary adapter let primaryHealthy = false; let primaryError; try { primaryHealthy = await this.adapter.healthCheck(); } catch (error) { primaryError = error instanceof Error ? error.message : String(error); } // Check fallback adapters const fallbackResults = await Promise.allSettled(this.fallbackAdapters.map(async (adapter) => { try { const healthy = await adapter.healthCheck(); return { name: adapter.getName(), healthy }; } catch (error) { return { name: adapter.getName(), healthy: false, error: error instanceof Error ? error.message : String(error), }; } })); const fallbacks = fallbackResults.map((result) => result.status === 'fulfilled' ? result.value : { name: 'unknown', healthy: false, error: 'Promise rejected' }); const healthStatus = { primary: { name: this.adapter.getName(), healthy: primaryHealthy, ...(primaryError && { error: primaryError }), }, fallbacks, }; this.logger.debug('Health check completed', healthStatus); return healthStatus; } }