Skip to main content
Glama

DeepResearch MCP

by ameeralns
index.ts12.3 kB
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import * as crypto from 'crypto'; import dotenv from 'dotenv'; // Import our services import { initializeResearch, executeResearchStep, getResearchState, generateResearchReport } from './services/research.js'; import { ResearchReport } from './types/index.js'; // Load environment variables dotenv.config(); // Create MCP server const server = new McpServer({ name: process.env.MCP_SERVER_NAME || 'DeepResearch', version: process.env.MCP_SERVER_VERSION || '1.0.0' }); // Resource to access research state server.resource( 'research-state', new ResourceTemplate('research://state/{sessionId}', { list: undefined }), async (uri, params) => { const sessionId = params.sessionId as string; const state = getResearchState(sessionId); if (!state) { throw new Error(`Research session not found: ${sessionId}`); } return { contents: [{ uri: uri.href, text: JSON.stringify(state, null, 2) }] }; } ); // Resource to access research findings server.resource( 'research-findings', new ResourceTemplate('research://findings/{sessionId}', { list: undefined }), async (uri, params) => { const sessionId = params.sessionId as string; const state = getResearchState(sessionId); if (!state) { throw new Error(`Research session not found: ${sessionId}`); } return { contents: [{ uri: uri.href, text: state.findings.join('\n\n---\n\n') }] }; } ); // Tool to initialize research server.tool( 'initialize-research', { query: z.string(), depth: z.number().default(3), }, async ({ query, depth }) => { try { // Generate a unique session ID const sessionId = crypto.randomUUID(); const state = initializeResearch(sessionId, query, depth); return { content: [{ type: 'text', text: JSON.stringify({ sessionId, message: 'Research session initialized', state }, null, 2) }] }; } catch (error) { console.error('Error initializing research:', error); return { content: [{ type: 'text', text: `Error initializing research: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } ); // Tool to execute a research step server.tool( 'execute-research-step', { sessionId: z.string(), }, async ({ sessionId }) => { try { const updatedState = await executeResearchStep(sessionId); return { content: [{ type: 'text', text: JSON.stringify({ message: 'Research step executed', currentDepth: updatedState.currentDepth, maxDepth: updatedState.depth, lastTopic: updatedState.topics[updatedState.topics.length - 1], nextTopic: updatedState.nextSearchTopic, shouldContinue: updatedState.shouldContinue, state: updatedState }, null, 2) }] }; } catch (error) { console.error('Error executing research step:', error); // Get the current state, even if there was an error const currentState = getResearchState(sessionId); // If we have a valid state, return a properly formatted JSON response with the error if (currentState) { return { content: [{ type: 'text', text: JSON.stringify({ message: `Error: ${error instanceof Error ? error.message : String(error)}`, currentDepth: currentState.currentDepth, maxDepth: currentState.depth, lastTopic: currentState.topics.length > 0 ? currentState.topics[currentState.topics.length - 1] : currentState.query, nextTopic: currentState.nextSearchTopic, shouldContinue: false, // Stop research on error state: currentState, error: true }, null, 2) }] }; } // Fallback if we can't get the current state return { content: [{ type: 'text', text: JSON.stringify({ message: `Error executing research step: ${error instanceof Error ? error.message : String(error)}`, error: true }, null, 2) }], isError: true }; } } ); // Tool to generate a final research report 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 }; } } ); // NEW TOOL: Complete Research in one step server.tool( 'complete-research', { query: z.string(), depth: z.number().default(3), timeout: z.number().optional().default(180000) // 3 minute timeout (same as client setting) }, async ({ query, depth, timeout }) => { // Define and initialize sessionId in the outer scope const sessionId = crypto.randomUUID(); try { // Step 1: Initialize research console.error(`Initializing research session for: "${query}" with depth ${depth}`); initializeResearch(sessionId, query, depth); console.error(`Research session initialized with ID: ${sessionId}`); // Step 2: Execute all research steps sequentially let currentDepth = 0; let stepData; while (currentDepth < depth) { console.error(`Executing research step ${currentDepth + 1}/${depth}...`); try { stepData = await executeResearchStep(sessionId); currentDepth = stepData.currentDepth; console.error(`Completed step ${currentDepth}/${depth}`); console.error(`Last topic searched: ${stepData.topics[stepData.topics.length - 1]}`); if (stepData.nextSearchTopic) { console.error(`Next topic to search: ${stepData.nextSearchTopic}`); } else { console.error(`No further topics to search.`); } } catch (stepError) { // Log the error but continue with next steps console.error(`Error in research step ${currentDepth + 1}: ${stepError}`); // Get the current state to determine the new depth const currentState = getResearchState(sessionId); if (currentState) { currentDepth = currentState.currentDepth; } else { // If we can't get the state, just increment manually currentDepth++; } } } // Step 3: Generate the final report with timeout handling console.error(`Generating final research report...`); // Create a promise that rejects after the timeout const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error('Report generation timed out')), timeout); }); // Race the report generation against the timeout const report = await Promise.race([ generateResearchReport(sessionId), timeoutPromise ]) as ResearchReport; return { content: [{ type: 'text', text: `# Complete Research Results for "${query}"\n\n` + `Research completed with depth: ${depth}\n\n` + `## Final Report\n\n${report.report}` }] }; } catch (error) { console.error('Error in complete research process:', error); // Get the current state to generate a fallback report const currentState = getResearchState(sessionId); 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 performing research: ${error instanceof Error ? error.message : String(error)}`, error: true }, null, 2) }], isError: true }; } } ); // Prompt for complete research flow server.prompt( 'deep-research', 'A deep research tool that explores topics thoroughly through iterative search', () => ({ messages: [{ role: 'user', content: { type: 'text', text: `Please conduct a deep research session on a topic of interest. I'm looking for a comprehensive analysis with multiple angles and perspectives. The research should explore the topic thoroughly, considering different viewpoints and citing reliable sources. Please follow these steps: 1. Initialize a research session with my topic 2. Perform multiple rounds of iterative research, exploring different aspects 3. Generate a comprehensive report with your findings` } }] }) ); // Start the server async function startServer() { const transport = new StdioServerTransport(); const serverName = server?.constructor?.name || 'DeepResearch'; const serverVersion = '1.0.0'; console.error(`Starting DeepResearch MCP Server (${serverName} v${serverVersion})`); try { await server.connect(transport); console.error('Server connected. Waiting for requests...'); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } } startServer().catch(console.error);

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