Skip to main content
Glama
synthesis-handlers.ts•13.4 kB
/** * Synthesis Handlers * * Handlers for research and synthesis tools, including topic research, * content synthesis, and enhanced synthesis with multi-source analysis. */ import { ResearchEnhancer } from '../services/research-enhancer.service.js'; import { ContentExtractor } from '../services/content-extractor.service.js'; import { EnhancedContentExtractor } from '../services/enhanced-content-extractor.service.js'; import { GoogleSearchService } from '../services/google-search.service.js'; import { MultiSourceSynthesizer } from '../services/multi-source-synthesizer.service.js'; import { OutputFormat } from '../types.js'; import { EnhancedWebpageContent } from '../types/enhanced-types.js'; export class SynthesisHandlers { private researchEnhancer: ResearchEnhancer; private contentExtractor: ContentExtractor; private enhancedContentExtractor: EnhancedContentExtractor; private searchService: GoogleSearchService; private multiSourceSynthesizer: MultiSourceSynthesizer; constructor( researchEnhancer: ResearchEnhancer, contentExtractor: ContentExtractor, enhancedContentExtractor: EnhancedContentExtractor, searchService: GoogleSearchService, multiSourceSynthesizer: MultiSourceSynthesizer ) { this.researchEnhancer = researchEnhancer; this.contentExtractor = contentExtractor; this.enhancedContentExtractor = enhancedContentExtractor; this.searchService = searchService; this.multiSourceSynthesizer = multiSourceSynthesizer; } /** * Handle research topic requests */ public async handleResearchTopic(args: any): Promise<any> { const { topic, depth, focus_areas, num_sources } = args; // Validate required arguments if (!topic) { throw new Error('Invalid arguments for research_topic tool: topic is required'); } try { // Set max sources and validate const maxSources = Math.min(Math.max(num_sources || 5, 3), 10); console.error(`Researching topic: "${topic}" with depth: ${depth || 'intermediate'}, max sources: ${maxSources}`); // Step 1: Perform multiple searches to gather diverse information // Use different query variations to get better coverage const searchQueries = [topic]; // Add depth-specific queries if (depth === 'advanced') { searchQueries.push(`${topic} research`, `${topic} detailed analysis`); } else if (depth === 'basic') { searchQueries.push(`${topic} overview`, `${topic} introduction`); } else { // Default to intermediate searchQueries.push(`${topic} explanation`, `${topic} guide`); } // Add focus areas to queries if provided if (focus_areas && focus_areas.length > 0) { focus_areas.forEach((area: string) => { searchQueries.push(`${topic} ${area}`); }); } // Execute all searches in parallel with source distribution const searchesPerQuery = Math.ceil(maxSources / searchQueries.length) + 1; // +1 for redundancy console.error(`Executing ${searchQueries.length} search queries with ${searchesPerQuery} results each`); const searchResults = await Promise.all( searchQueries.map(query => this.searchService.search(query, searchesPerQuery)) ); // Combine and deduplicate results let allResults: Array<{title: string; link: string; snippet: string}> = []; searchResults.forEach(result => { allResults = [...allResults, ...result.results]; }); // Filter out duplicate URLs and take top N results const uniqueUrls = new Set<string>(); const filteredResults = allResults .filter(result => { if (uniqueUrls.has(result.link)) return false; uniqueUrls.add(result.link); return true; }) .slice(0, maxSources); if (filteredResults.length === 0) { return { content: [{ type: 'text', text: `No results found for "${topic}". Try using different keywords or a more general topic.` }], isError: true }; } console.error(`Found ${filteredResults.length} unique sources for research`); // Step 2: Extract enhanced content from the sources const urls = filteredResults.map(result => result.link); console.error(`Extracting enhanced content from ${urls.length} sources...`); // Use enhanced content extractor for better data const contentPromises = urls.map(url => this.enhancedContentExtractor.extractEnhancedContent(url, 'markdown') .catch(error => ({ error: error instanceof Error ? error.message : 'Unknown error occurred' })) ); const enhancedContents = await Promise.all(contentPromises); // Filter out failed extractions const validContents = enhancedContents.filter((content): content is EnhancedWebpageContent => !('error' in content) ); if (validContents.length === 0) { return { content: [{ type: 'text', text: `Could not extract valid content from any sources for "${topic}". Please try a different topic or keywords.` }], isError: true }; } console.error(`Successfully extracted content from ${validContents.length}/${urls.length} sources`); // Step 3: Use the research enhancer to create a structured research document console.error(`Generating research document for "${topic}"`); const researchDocument = await this.researchEnhancer.createResearchDocument({ topic, depth: depth || 'intermediate', focus_areas, sources: enhancedContents.map((content, index) => { if ('error' in content) { return { url: urls[index], error: content.error }; } return { url: content.url, title: content.title, content: content.content, summary: content.summary || '' }; }) }); return { content: [ { type: 'text', text: researchDocument, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { content: [ { type: 'text', text: `Error researching "${topic}": ${errorMessage}`, }, ], isError: true, }; } } /** * Handle content synthesis requests */ public async handleSynthesizeContent(args: any): Promise<any> { const { urls, focus, structure } = args; // Validate required arguments if (!urls || !Array.isArray(urls) || urls.length === 0) { throw new Error('Invalid arguments for synthesize_content tool: urls array is required'); } if (urls.length > 10) { return { content: [{ type: 'text', text: 'Maximum 10 URLs allowed per synthesis to maintain quality. Please reduce the number of URLs.' }], isError: true }; } try { console.error(`Synthesizing content from ${urls.length} sources using ${structure || 'thematic'} structure`); // Extract enhanced content from all URLs to get better data for synthesis console.error('Extracting enhanced content from provided URLs...'); // Use enhanced content extractor for better structure preservation const contentPromises = urls.map(url => this.enhancedContentExtractor.extractEnhancedContent(url, 'markdown') .catch(error => ({ error: error instanceof Error ? error.message : 'Unknown error occurred' })) ); const enhancedContents = await Promise.all(contentPromises); // Filter out failed extractions const validContents = enhancedContents.filter((content): content is EnhancedWebpageContent => !('error' in content) ); if (validContents.length === 0) { return { content: [{ type: 'text', text: 'Could not extract valid content from any of the provided URLs. Please check the URLs and try again.' }], isError: true }; } console.error(`Successfully extracted content from ${validContents.length}/${urls.length} sources`); // Use the multi-source synthesizer instead of research enhancer // This leverages the more advanced synthesis capabilities console.error(`Generating synthesized content with focus: "${focus || 'general'}"`); const result = await this.multiSourceSynthesizer.synthesize({ sources: validContents, focus, structure: structure || 'thematic', compareBy: [], detectContradictions: true, includeSourceCredibility: true, visualizeRelationships: true }); const synthesizedContent = result.document; return { content: [ { type: 'text', text: synthesizedContent, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { content: [ { type: 'text', text: `Error synthesizing content: ${errorMessage}`, }, ], isError: true, }; } } /** * Handle enhanced synthesis requests */ public async handleEnhancedSynthesis(args: any): Promise<any> { const { urls, focus, structure, detect_contradictions, assess_credibility, compare_by } = args; // Validate required arguments if (!urls || !Array.isArray(urls) || urls.length === 0) { throw new Error('Invalid arguments for enhanced_synthesis tool: urls array is required'); } try { console.error(`Performing enhanced synthesis of ${urls.length} sources`); if (urls.length > 10) { return { content: [{ type: 'text', text: 'Maximum 10 URLs allowed per synthesis to maintain quality. Please reduce the number of URLs.' }], isError: true }; } // Extract enhanced content from all URLs const contentPromises = urls.map(url => this.enhancedContentExtractor.extractEnhancedContent(url, 'markdown') .catch(error => ({ url, title: url, content: '', format: 'markdown' as OutputFormat, description: '', meta_tags: {}, stats: { word_count: 0, approximate_chars: 0 }, content_preview: { first_500_chars: '' }, links: [], images: [], structuredData: { tables: [], lists: [], hierarchies: [], keyValuePairs: [] }, error: error instanceof Error ? error.message : 'Unknown error' })) ); const sources = await Promise.all(contentPromises); // Filter out failed extractions const validSources = sources.filter((source): source is EnhancedWebpageContent => !('error' in source) ); if (validSources.length === 0) { return { content: [{ type: 'text', text: 'Could not extract valid content from any of the provided URLs. Please check the URLs and try again.' }], isError: true }; } // Perform enhanced synthesis const result = await this.multiSourceSynthesizer.synthesize({ sources: validSources, focus, structure: structure || 'thematic', compareBy: compare_by, detectContradictions: detect_contradictions !== false, includeSourceCredibility: assess_credibility !== false, visualizeRelationships: true }); return { content: [ { type: 'text', text: result.document, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { content: [ { type: 'text', text: `Error performing enhanced synthesis: ${errorMessage}`, }, ], isError: true, }; } } /** * Get all handlers as a map of tool name to handler function */ public getHandlers(): Record<string, (args: any) => Promise<any>> { return { 'research_topic': this.handleResearchTopic.bind(this), 'synthesize_content': this.handleSynthesizeContent.bind(this), 'enhanced_synthesis': this.handleEnhancedSynthesis.bind(this) }; } }

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/mixelpixx/Google-Research-MCP'

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