Skip to main content
Glama
ameeralns

DeepResearch MCP

by ameeralns

generate-report

Automates comprehensive report generation by analyzing specified topics, enabling users to obtain structured insights via web research and data synthesis.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sessionIdYes
timeoutNo

Implementation Reference

  • src/index.ts:161-221 (registration)
    Full registration of the 'generate-report' tool, including input schema, handler function with timeout and error handling that invokes generateResearchReport.
    server.tool(
      'generate-report',
      {
        sessionId: z.string(),
        timeout: z.number().optional().default(60000)
      },
      async ({ sessionId, timeout }) => {
        // Create a promise that rejects after the timeout
        const timeoutPromise = new Promise((_, reject) => {
          setTimeout(() => reject(new Error('Report generation timed out')), timeout);
        });
    
        try {
          // Race the report generation against the timeout
          const report = await Promise.race([
            generateResearchReport(sessionId),
            timeoutPromise
          ]) as ResearchReport;
          
          return {
            content: [{
              type: 'text',
              text: report.report
            }]
          };
        } catch (error) {
          console.error('Error generating research report:', error);
          
          // Get the current state, even if there was an error
          const currentState = getResearchState(sessionId);
          
          // If we have a valid state, try to generate a basic report from what we have
          if (currentState && currentState.findings.length > 0) {
            return {
              content: [{
                type: 'text',
                text: `# Research Report (Error Recovery)\n\n` +
                      `**Original Query:** ${currentState.query}\n\n` +
                      `**Note:** This is a partial report generated after an error occurred: ${error instanceof Error ? error.message : String(error)}\n\n` +
                      `## Summary of Findings\n\n` +
                      `The research process collected ${currentState.findings.length} sets of findings ` +
                      `across ${currentState.topics.length} topics but encountered an error during the final report generation.\n\n` +
                      `### Topics Researched\n\n` +
                      currentState.topics.map((topic, index) => `${index + 1}. ${topic}`).join('\n')
              }]
            };
          }
          
          return {
            content: [{
              type: 'text',
              text: JSON.stringify({
                message: `Error generating research report: ${error instanceof Error ? error.message : String(error)}`,
                error: true
              }, null, 2)
            }],
            isError: true
          };
        }
      }
    );
  • Helper function generateResearchReport that fetches the research state for the session and calls the OpenAI generateReport service to produce the final structured report.
    export async function generateResearchReport(sessionId: string): Promise<ResearchReport> {
      const researchState = researchSessions.get(sessionId);
      if (!researchState) {
        throw new Error(`No research session found with ID: ${sessionId}`);
      }
    
      const report = await generateReport(researchState.query, researchState.findings);
      
      return {
        query: researchState.query,
        findings: researchState.findings,
        topics: researchState.topics,
        report
      };
    }
  • Core implementation logic in generateReport: parses findings into sources, optimizes content to fit token limits using truncation and metadata extraction, prompts GPT-4-turbo to synthesize a structured research report with citations and bibliography.
    export async function generateReport(
      query: string,
      findings: string[]
    ): Promise<string> {
      try {
        // Extract all sources and their content into a structured format
        interface SourceContent {
          url: string;
          title: string;
          content: string;
          sourceNum: number;
          searchQuery: string;
        }
        
        // Track all sources and their content
        const allSources: SourceContent[] = [];
        const sourceUrlMap: Map<string, number> = new Map(); // URL to source number mapping
        let globalSourceCounter = 0;
        
        // Process each finding to extract structured content
        findings.forEach((finding, findingIndex) => {
          // Extract search query
          const searchQueryMatch = finding.match(/# Search Results for: (.*?)(\n|$)/);
          const searchQuery = searchQueryMatch ? searchQueryMatch[1] : `Finding ${findingIndex + 1}`;
          
          // Process each source in the finding
          let isInContent = false;
          let contentBuffer: string[] = [];
          let currentUrl = '';
          let currentTitle = '';
          let currentSourceNum = 0;
          
          // Split the finding into lines for processing
          finding.split('\n').forEach(line => {
            // Source header pattern: ## Source [1]: Title
            const sourceMatch = line.match(/## Source \[(\d+)\]: (.*?)$/);
            if (sourceMatch) {
              currentSourceNum = parseInt(sourceMatch[1]);
              currentTitle = sourceMatch[2];
              isInContent = false;
              
              // If we were processing a previous source, finalize it
              if (contentBuffer.length > 0 && currentUrl) {
                // Avoid duplicating content from the same URL
                if (!sourceUrlMap.has(currentUrl)) {
                  globalSourceCounter++;
                  sourceUrlMap.set(currentUrl, globalSourceCounter);
                  
                  allSources.push({
                    url: currentUrl,
                    title: currentTitle,
                    content: contentBuffer.join('\n'),
                    sourceNum: globalSourceCounter,
                    searchQuery
                  });
                }
                
                contentBuffer = [];
                currentUrl = '';
              }
            }
            // URL pattern: URL: https://...
            else if (line.startsWith('URL: ')) {
              currentUrl = line.substring(5).trim();
            }
            // Content header pattern: ### Content from Source [1]:
            else if (line.match(/### Content from Source \[\d+\]:/)) {
              isInContent = true;
              contentBuffer = [];
            }
            // End of source content (next source starts or end of finding)
            else if (isInContent && (line.startsWith('## Source') || line.startsWith('# Source URLs'))) {
              isInContent = false;
              
              // Finalize the current source
              if (contentBuffer.length > 0 && currentUrl) {
                // Avoid duplicating content from the same URL
                if (!sourceUrlMap.has(currentUrl)) {
                  globalSourceCounter++;
                  sourceUrlMap.set(currentUrl, globalSourceCounter);
                  
                  allSources.push({
                    url: currentUrl,
                    title: currentTitle,
                    content: contentBuffer.join('\n'),
                    sourceNum: globalSourceCounter,
                    searchQuery
                  });
                }
                
                contentBuffer = [];
                currentUrl = '';
              }
              
              // No continue or break needed - just let it naturally move to the next line
            } else if (isInContent) {
              contentBuffer.push(line);
            }
          });
        });
        
        console.error(`Extracted ${allSources.length} sources from ${findings.length} findings`);
        
        // More aggressive content optimization
        // 1. Set a much lower character limit for content
        const MAX_CONTENT_LENGTH = 40000; // Reduced from 60000 to 40000 characters
        let totalContentLength = 0;
        
        // 2. Calculate total content length
        allSources.forEach(source => {
          totalContentLength += source.content.length;
        });
        
        // 3. Group sources by search query
        const sourcesByQuery = new Map<string, SourceContent[]>();
        allSources.forEach(source => {
          if (!sourcesByQuery.has(source.searchQuery)) {
            sourcesByQuery.set(source.searchQuery, []);
          }
          sourcesByQuery.get(source.searchQuery)?.push(source);
        });
        
        // 4. If content is too large, trim it intelligently
        let optimizedContent = '';
        
        if (totalContentLength > MAX_CONTENT_LENGTH) {
          console.error(`Content exceeds token limit (${totalContentLength} characters), optimizing...`);
          
          // 5. Instead of proportional allocation, use a more aggressive summarization approach
          // Create a structured bibliography with minimal content
          optimizedContent = '# BIBLIOGRAPHY\n\n';
          
          // First pass: Add only metadata for each source
          sourcesByQuery.forEach((sources, query) => {
            optimizedContent += `## Search Query: ${query}\n\n`;
            
            sources.forEach(source => {
              // Just add metadata and URL for each source, no content
              optimizedContent += `[${source.sourceNum}] "${source.title}"\n`;
              optimizedContent += `URL: ${source.url}\n\n`;
            });
          });
          
          // Second pass: Add abbreviated content for each source until we reach the limit
          let currentLength = optimizedContent.length;
          const remainingLength = MAX_CONTENT_LENGTH - currentLength;
          
          // Calculate how many characters we can allocate per source
          const maxCharsPerSource = Math.floor(remainingLength / allSources.length);
          
          // Add additional section for content excerpts
          optimizedContent += '# CONTENT EXCERPTS\n\n';
          
          // Add abbreviated content for each source
          allSources.forEach(source => {
            // Truncate the content to the allocated size
            const excerpt = source.content.length > maxCharsPerSource 
              ? source.content.substring(0, maxCharsPerSource) + '...'
              : source.content;
            
            optimizedContent += `## [${source.sourceNum}] ${source.title}\n\n`;
            optimizedContent += `${excerpt}\n\n`;
          });
        } else {
          // If content is within limits, use the original approach
          sourcesByQuery.forEach((sources, query) => {
            optimizedContent += `## Search Query: ${query}\n\n`;
            
            sources.forEach(source => {
              optimizedContent += `### [${source.sourceNum}] ${source.title}\n`;
              optimizedContent += `URL: ${source.url}\n\n`;
              optimizedContent += `${source.content.trim()}\n\n`;
            });
          });
        }
    
        // Now generate the report with the optimized content
        console.error(`Generating report with optimized content (${optimizedContent.length} characters)`);
        
        // More optimized prompt with fewer instructions
        const response = await openai.chat.completions.create({
          model: 'gpt-4-turbo',
          messages: [
            {
              role: 'system',
              content: `Generate a concise research report on "${query}" using the provided sources. 
    Format:
    - Executive Summary (2-3 paragraphs)
    - Introduction
    - Main Findings (organized by themes)
    - Conclusion
    - Bibliography
    
    Cite sources using [X] format. Focus on key insights rather than exhaustive detail.`
            },
            {
              role: 'user',
              content: `Research report on "${query}" based on the following:
    
    ${optimizedContent}`
            }
          ],
          temperature: 0.5, // Lower temperature for more focused output
          max_tokens: 4000
        });
    
        if (!response.choices[0]?.message?.content) {
          throw new Error("No response content from OpenAI API");
        }
    
        return response.choices[0].message.content;
      } catch (error) {
        console.error("[OpenAI Service] Error generating report:", error);
        throw error;
      }
    } 
  • TypeScript interface defining the structure of the ResearchReport returned by the tool's handler.
    export interface ResearchReport {
      query: string;
      findings: string[];
      topics: string[];
      report: string;
    } 

Tool Definition Quality

Score is being calculated. Check back soon.

Install Server

Other Tools

Related Tools

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/ameeralns/DeepResearchMCP'

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