marketing-post-generator-mcp
Version:
A powerful MCP server for AI-powered marketing blog post generation with Claude integration
293 lines • 11.9 kB
JavaScript
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;
}
}
//# sourceMappingURL=SearchService.js.map