#
tokens: 12593/50000 14/14 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── index.ts
│   ├── mcp.ts
│   ├── tools
│   │   ├── braveSearch.ts
│   │   ├── deepResearch.ts
│   │   └── sequentialThinking.ts
│   ├── types
│   │   └── mcp-sdk.d.ts
│   ├── types.ts
│   ├── utils
│   │   ├── analysis.ts
│   │   ├── question.ts
│   │   ├── search.ts
│   │   └── synthesis.ts
│   └── websocket
│       └── server.ts
├── test-server.sh
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/

# Build
dist/

# Environment variables
.env

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# OS
.DS_Store
Thumbs.db 

# Thrash
/downloads
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# OpenDeepSearch

An open-source alternative to Perplexity Deep Research using the Model Context Protocol (MCP).

## Overview

OpenDeepSearch is a powerful research tool that performs comprehensive, in-depth research on complex topics. It combines the structured thinking approach of Sequential Thinking with the search capabilities of Brave Search to provide detailed, well-sourced research reports.

## Features

- **Comprehensive Research**: Breaks down complex questions into manageable sub-questions
- **Iterative Search**: Performs multiple searches to gather diverse information
- **Intelligent Analysis**: Analyzes search results to extract relevant information
- **Synthesis**: Combines findings into a coherent, well-structured report
- **Citations**: Includes sources for all information in the report
- **MCP Integration**: Seamlessly integrates with Claude Desktop, Cursor, and other MCP clients
- **WebSockets**: Supports integration with Smithery and other MCP clients
- **Publication**: Allows publishing the research tool on the Smithery platform for easy access

## Installation

### Prerequisites

- Node.js 16 or higher
- A Brave Search API key (get one at [https://brave.com/search/api/](https://brave.com/search/api/))

### NPM Installation

```bash
npm install -g open-deep-research
```

### Running with NPX

```bash
BRAVE_API_KEY=your_api_key npx open-deep-research
```

### Local Installation

```bash
# Clone the repository
git clone https://github.com/tositon/open-deep-research.git
cd open-deep-research

# Install dependencies
npm install

# Build the project
npm run build

# Run with Brave Search API
BRAVE_API_KEY=your_api_key npm start
```

### Installation via Smithery

```bash
# Install for Claude
npx @smithery/cli install open-deep-research --client claude

# Install for Cursor
npx @smithery/cli install open-deep-research --client cursor
```

When installing via Smithery, you will be prompted to enter a Brave Search API key.

## Usage

### With Claude Desktop

Add the following to your Claude Desktop configuration:

```json
{
  "mcpServers": {
    "open-deep-research": {
      "command": "npx",
      "args": [
        "-y",
        "open-deep-research"
      ],
      "env": {
        "BRAVE_API_KEY": "your_api_key_here"
      }
    }
  }
}
```

### With Cursor

In Cursor, you can add the MCP server with:

```
claude mcp add "open-deep-research" npx open-deep-research
```

Make sure to set the `BRAVE_API_KEY` environment variable before running Cursor.

### Example Queries

- "What are the latest developments in quantum computing?"
- "Compare and contrast different approaches to climate change mitigation"
- "Explain the history and impact of the Renaissance on European art"
- "What are the pros and cons of different renewable energy sources?"

## How It Works

1. **Question Analysis**: The system analyzes the main question and breaks it down into sub-questions
2. **Iterative Search**: For each sub-question, the system performs searches using Brave Search API
3. **Result Analysis**: The system analyzes the search results to extract relevant information
4. **Synthesis**: The system combines the findings into a coherent report
5. **Citation**: All information is properly cited with sources

## Development

### Setup

```bash
git clone https://github.com/tositon/open-deep-research.git
cd open-deep-research
npm install
```

### Build

```bash
npm run build
```

### Run in Development Mode

```bash
BRAVE_API_KEY=your_api_key npm run dev
```

## Testing

### Testing with MCP Inspector

Для тестирования MCP сервера можно использовать MCP Inspector, который предоставляет удобный интерфейс для взаимодействия с инструментами:

```bash
# Установка и запуск MCP Inspector
npx @modelcontextprotocol/inspector

# Запуск сервера в другом терминале
BRAVE_API_KEY=your_api_key npm start
```

После запуска Inspector, откройте браузер и перейдите по адресу http://localhost:5173. Подключитесь к WebSocket серверу, используя URL `ws://localhost:3000`.

### Примеры запросов для тестирования инструментов

В интерфейсе MCP Inspector вы можете выбрать инструмент и настроить параметры запроса:

#### Тестирование Brave Web Search

```json
{
  "query": "latest quantum computing advancements",
  "count": 5
}
```

#### Тестирование Sequential Thinking

```json
{
  "thought": "Начинаю анализ проблемы глобального потепления",
  "thoughtNumber": 1,
  "totalThoughts": 5,
  "nextThoughtNeeded": true
}
```

#### Тестирование Deep Research

```json
{
  "query": "Сравнение различных источников возобновляемой энергии",
  "action": "start",
  "maxSubQuestions": 3
}
```

### Testing with Claude or Cursor

После установки сервера через Smithery или локально, вы можете использовать его с Claude Desktop или Cursor, выбрав соответствующий MCP сервер в настройках.

## Publishing on Smithery

To publish the server on the Smithery platform:

1. Ensure the repository is hosted on GitHub and is public
2. Register on the [Smithery](https://smithery.ai/) platform
3. Authenticate via GitHub to connect with the repository
4. Go to the "Deployments" tab on the server page
5. Click the "Deploy on Smithery" button
6. Follow the deployment setup instructions

After publishing, users can install the server using the Smithery CLI:

```bash
npx @smithery/cli install open-deep-research --client claude
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

This project is licensed under the MIT License - see the LICENSE file for details.

## Acknowledgments

- Inspired by Perplexity Deep Research
- Built on the Model Context Protocol
- Uses Sequential Thinking approach for structured research
- Powered by Brave Search API 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist",
    "declaration": true,
    "sourceMap": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
} 
```

--------------------------------------------------------------------------------
/src/types/mcp-sdk.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module '@modelcontextprotocol/sdk/server/mcp.js' {
  export class McpServer {
    constructor(options: {
      name: string;
      version: string;
      description: string;
    });

    tool(
      name: string, 
      description: string,
      paramsSchema: any,
      handler: (params: any) => Promise<any>
    ): void;

    connect(transport: any): Promise<void>;
  }
} 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "open-deep-research",
  "version": "0.1.0",
  "description": "An open-source alternative to Perplexity Deep Research using MCP protocol",
  "main": "dist/index.js",
  "type": "module",
  "bin": {
    "open-deep-research": "dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts",
    "lint": "eslint src --ext .ts",
    "format": "prettier --write \"src/**/*.ts\"",
    "prepublishOnly": "npm run build",
    "test": "./test-server.sh",
    "clean": "rm -rf dist",
    "rebuild": "npm run clean && npm run build"
  },
  "keywords": [
    "mcp",
    "deep-research",
    "perplexity",
    "ai",
    "search",
    "research",
    "cursor"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "axios": "^1.8.3",
    "chalk": "^5.4.1",
    "dotenv": "^16.4.7",
    "uuid": "^9.0.1",
    "ws": "^8.18.1"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "@types/uuid": "^9.0.7",
    "@types/ws": "^8.18.0",
    "eslint": "^8.54.0",
    "prettier": "^3.1.0",
    "tsx": "^4.6.0",
    "typescript": "^5.3.2"
  },
  "engines": {
    "node": ">=16.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/tositon/OpenDeepSearch.git"
  },
  "bugs": {
    "url": "https://github.com/tositon/OpenDeepSearch/issues"
  },
  "homepage": "https://github.com/tositon/OpenDeepSearch#readme"
}

```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Types and interfaces for OpenDeepSearch
 */

// Type of research step
export enum ResearchStepType {
  QUESTION_ANALYSIS = 'question_analysis',
  SEARCH = 'search',
  RESULT_ANALYSIS = 'result_analysis',
  SYNTHESIS = 'synthesis',
  FOLLOW_UP = 'follow_up'
}

// Status of research
export enum ResearchStatus {
  PLANNING = 'planning',
  SEARCHING = 'searching',
  ANALYZING = 'analyzing',
  SYNTHESIZING = 'synthesizing',
  COMPLETED = 'completed'
}

// Data for one research step
export interface ResearchStep {
  id: string;
  type: ResearchStepType;
  content: string;
  timestamp: number;
  metadata?: Record<string, any>;
}

// Sub-question
export interface SubQuestion {
  id: string;
  question: string;
  status: 'pending' | 'in-progress' | 'completed';
  searchResults?: SearchResult[];
  analysis?: string;
}

// Search result
export interface SearchResult {
  title: string;
  description: string;
  url: string;
  relevance?: number; // Relevance score from 0 to 1
}

// Complete research data
export interface ResearchData {
  id: string;
  question: string;
  subQuestions: SubQuestion[];
  steps: ResearchStep[];
  status: ResearchStatus;
  report?: string;
  startTime: number;
  endTime?: number;
}

// Options for the server
export interface DeepResearchOptions {
  braveApiKey: string;
  maxSubQuestions?: number; // Maximum number of sub-questions
  maxSearchesPerQuestion?: number; // Maximum number of searches per sub-question
  maxTotalSteps?: number; // Maximum number of research steps
  timeout?: number; // Timeout in milliseconds
} 
```

--------------------------------------------------------------------------------
/src/mcp.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Type definitions for Model Context Protocol (MCP)
 * Since we don't have access to the official SDK, we'll define our own types
 */

/**
 * MCP Tool Definition
 */
export interface MCPToolDefinition {
  name: string;
  description: string;
  parameters: {
    type: string;
    properties: Record<string, any>;
    required?: string[];
  };
}

/**
 * MCP Tool Response
 */
export interface MCPToolResponse {
  status: 'success' | 'error';
  result?: any;
  error?: string;
}

/**
 * MCP Tool Interface
 */
export interface MCPTool {
  getDefinition(): MCPToolDefinition;
  execute(params: any): Promise<MCPToolResponse>;
}

/**
 * MCP Server Options
 */
export interface MCPServerOptions {
  name: string;
  version: string;
  description: string;
}

/**
 * MCP Server
 */
export class MCPServer {
  private options: MCPServerOptions;
  private tools: Map<string, MCPTool> = new Map();

  constructor(options: MCPServerOptions) {
    this.options = options;
  }

  /**
   * Register a tool with the server
   * @param tool The tool to register
   */
  registerTool(tool: MCPTool): void {
    const definition = tool.getDefinition();
    this.tools.set(definition.name, tool);
  }

  /**
   * Start the MCP server
   * This is a simplified implementation that doesn't actually start a server
   * In a real implementation, this would start a WebSocket server
   */
  async start(): Promise<void> {
    // In a real implementation, this would start a WebSocket server
    // For now, we'll just log that the server is starting
    console.log(`Starting MCP server: ${this.options.name} v${this.options.version}`);
    console.log(`Description: ${this.options.description}`);
    console.log(`Registered tools: ${Array.from(this.tools.keys()).join(', ')}`);
  }

  /**
   * Stop the MCP server
   */
  async stop(): Promise<void> {
    // In a real implementation, this would stop the WebSocket server
    console.log(`Stopping MCP server: ${this.options.name}`);
  }
} 
```

--------------------------------------------------------------------------------
/src/utils/question.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utilities for working with questions
 */

// Если в будущем будут добавлены импорты, они должны включать расширение .js
// import { SomeType } from '../types.js';

/**
 * Analyzes the main question and breaks it down into sub-questions
 * @param question The main question
 * @returns Array of sub-questions
 */
export async function analyzeQuestion(question: string): Promise<string[]> {
  // In a real implementation, this could use an LLM for question decomposition
  // For the prototype, we'll use a simple algorithm
  
  // Remove question marks and split by "and", "or", commas
  const cleanQuestion = question.replace(/\?/g, '').trim();
  
  // Look for keywords that might indicate a compound question
  const conjunctions = ['and', 'or', 'versus', 'vs', 'compared to', 'differences between'];
  let hasConjunction = false;
  
  for (const conj of conjunctions) {
    if (cleanQuestion.toLowerCase().includes(conj)) {
      hasConjunction = true;
      break;
    }
  }
  
  // If the question contains conjunctions, break it down
  if (hasConjunction) {
    // Simple heuristic for breaking down the question
    // In a real implementation, this would be more sophisticated
    const parts = cleanQuestion.split(/\s+(?:and|or|versus|vs|compared to)\s+/i);
    
    if (parts.length > 1) {
      // Form sub-questions based on parts
      return parts.map(part => `${part.trim()}?`);
    }
  }
  
  // If we couldn't break it down by conjunctions, create sub-questions by key aspects
  // This is a simplified version, a real implementation would need a more complex algorithm
  const aspects = [
    'what is', 'how does', 'why is', 'when was', 'where is',
    'definition', 'history', 'examples', 'advantages', 'disadvantages'
  ];
  
  const subQuestions = [];
  
  // Add the main question
  subQuestions.push(question);
  
  // Add sub-questions by aspects
  const mainTopic = extractMainTopic(cleanQuestion);
  if (mainTopic) {
    // Add several sub-questions on different aspects
    subQuestions.push(`What is ${mainTopic}?`);
    subQuestions.push(`What are the key features of ${mainTopic}?`);
    subQuestions.push(`What are the applications of ${mainTopic}?`);
  }
  
  // Remove duplicates and return unique sub-questions
  return Array.from(new Set(subQuestions));
}

/**
 * Extracts the main topic from a question
 * @param question The question
 * @returns The main topic or null if it couldn't be determined
 */
function extractMainTopic(question: string): string | null {
  // Remove question words at the beginning
  const withoutQuestionWords = question
    .replace(/^(what|who|when|where|why|how|is|are|do|does|did|can|could|would|should|will)\s+/i, '')
    .trim();
  
  // If the question starts with "the", "a", "an", remove the article
  const withoutArticles = withoutQuestionWords
    .replace(/^(the|a|an)\s+/i, '')
    .trim();
  
  // If there's anything left, return it as the main topic
  return withoutArticles.length > 0 ? withoutArticles : null;
} 
```

--------------------------------------------------------------------------------
/src/utils/search.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utilities for performing searches using Brave Search API
 */

import axios from 'axios';
import { SearchResult } from '../types.js';

/**
 * Performs a search using the Brave Search API
 * @param query The search query
 * @param apiKey The Brave Search API key
 * @param count The number of results to return (max 20)
 * @returns Array of search results
 */
export async function performSearch(
  query: string,
  apiKey: string,
  count: number = 10
): Promise<SearchResult[]> {
  if (!query) {
    throw new Error('Search query is required');
  }

  if (!apiKey) {
    throw new Error('Brave Search API key is required');
  }

  // Limit count to maximum of 20 results
  const limitedCount = Math.min(count, 20);

  try {
    // Construct the API request URL
    const url = `https://api.search.brave.com/res/v1/web/search`;
    
    // Make the API request
    const response = await axios.get(url, {
      headers: {
        'Accept': 'application/json',
        'Accept-Encoding': 'gzip',
        'X-Subscription-Token': apiKey
      },
      params: {
        q: query,
        count: limitedCount
      }
    });

    // Check if the response is valid
    if (!response.data || !response.data.web || !response.data.web.results) {
      return [];
    }

    // Parse the results
    const results: SearchResult[] = response.data.web.results.map((result: any) => {
      return {
        title: result.title || '',
        url: result.url || '',
        description: result.description || '',
        relevance: calculateRelevance(query, result.title, result.description)
      };
    });

    return results;
  } catch (error) {
    console.error('Error performing search:', error);
    throw new Error(`Failed to perform search: ${error instanceof Error ? error.message : String(error)}`);
  }
}

/**
 * Calculates the relevance score of a search result
 * @param query The search query
 * @param title The result title
 * @param description The result description
 * @returns A relevance score between 0 and 1
 */
function calculateRelevance(query: string, title: string, description: string): number {
  // Normalize the query and result text
  const normalizedQuery = query.toLowerCase();
  const normalizedTitle = title.toLowerCase();
  const normalizedDescription = description.toLowerCase();
  
  // Split the query into words
  const queryWords = normalizedQuery.split(/\s+/).filter(word => word.length > 2);
  
  // Count matches in title (weighted higher)
  let titleMatches = 0;
  for (const word of queryWords) {
    if (normalizedTitle.includes(word)) {
      titleMatches++;
    }
  }
  
  // Count matches in description
  let descriptionMatches = 0;
  for (const word of queryWords) {
    if (normalizedDescription.includes(word)) {
      descriptionMatches++;
    }
  }
  
  // Calculate relevance score (title matches weighted 3x)
  const maxPossibleScore = queryWords.length * 4; // 3 for title + 1 for description
  const actualScore = (titleMatches * 3) + descriptionMatches;
  
  // Return normalized score between 0 and 1
  return maxPossibleScore > 0 ? actualScore / maxPossibleScore : 0;
} 
```

--------------------------------------------------------------------------------
/src/utils/analysis.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utilities for analyzing search results
 */

import { SearchResult } from '../types.js';

/**
 * Analyzes search results and extracts key information
 * @param results Array of search results
 * @param query The original search query
 * @returns Analysis report
 */
export async function analyzeResults(
  results: SearchResult[],
  query: string
): Promise<string> {
  if (!results || results.length === 0) {
    return `No results found for query: "${query}"`;
  }

  // Sort results by relevance
  const sortedResults = [...results].sort((a, b) => {
    const relevanceA = a.relevance ?? 0;
    const relevanceB = b.relevance ?? 0;
    return relevanceB - relevanceA;
  });
  
  // Take the top 5 most relevant results
  const topResults = sortedResults.slice(0, 5);
  
  // Extract key information from each result
  const analysisPoints = topResults.map((result, index) => {
    const keyInfo = extractKeyInformation(result, query);
    return `${index + 1}. ${result.title}\n   ${keyInfo}\n   Source: ${result.url}`;
  });
  
  // Format the analysis report
  const report = `
Analysis for query: "${query}"

Key Information:
${analysisPoints.join('\n\n')}

This analysis is based on the top ${topResults.length} most relevant results.
`;

  return report;
}

/**
 * Extracts key information from a search result
 * @param result The search result
 * @param query The original search query
 * @returns Extracted key information
 */
function extractKeyInformation(result: SearchResult, query: string): string {
  // In a real implementation, this would use NLP techniques
  // For the prototype, we'll use a simple approach
  
  // Get the most relevant sentences from the description
  const relevantSentences = selectRelevantSentences(result.description, query, 2);
  
  return relevantSentences.join(' ');
}

/**
 * Selects the most relevant sentences from a text
 * @param text The text to analyze
 * @param query The search query
 * @param maxSentences Maximum number of sentences to return
 * @returns Array of relevant sentences
 */
function selectRelevantSentences(text: string, query: string, maxSentences: number): string[] {
  // Split text into sentences
  const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
  
  if (sentences.length === 0) {
    return [text];
  }
  
  // Calculate relevance score for each sentence
  const scoredSentences = sentences.map(sentence => {
    const score = calculateSentenceRelevance(sentence, query);
    return { sentence, score };
  });
  
  // Sort by relevance score
  scoredSentences.sort((a, b) => b.score - a.score);
  
  // Return the top N sentences
  return scoredSentences.slice(0, maxSentences).map(s => s.sentence.trim());
}

/**
 * Calculates the relevance of a sentence to a query
 * @param sentence The sentence
 * @param query The query
 * @returns A relevance score
 */
function calculateSentenceRelevance(sentence: string, query: string): number {
  const normalizedSentence = sentence.toLowerCase();
  const normalizedQuery = query.toLowerCase();
  
  // Split query into words
  const queryWords = normalizedQuery.split(/\s+/).filter(word => !isStopWord(word) && word.length > 2);
  
  // Count how many query words appear in the sentence
  let matchCount = 0;
  for (const word of queryWords) {
    if (normalizedSentence.includes(word)) {
      matchCount++;
    }
  }
  
  // Calculate score based on match percentage and sentence length
  const matchPercentage = queryWords.length > 0 ? matchCount / queryWords.length : 0;
  const lengthFactor = 1 - Math.min(Math.abs(normalizedSentence.length - 100) / 100, 0.5);
  
  return matchPercentage * 0.7 + lengthFactor * 0.3;
}

/**
 * Checks if a word is a common stop word
 * @param word The word to check
 * @returns True if it's a stop word
 */
function isStopWord(word: string): boolean {
  const stopWords = [
    'a', 'an', 'the', 'and', 'or', 'but', 'if', 'because', 'as', 'what',
    'which', 'this', 'that', 'these', 'those', 'then', 'just', 'so', 'than',
    'such', 'both', 'through', 'about', 'for', 'is', 'of', 'while', 'during',
    'to', 'from', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further',
    'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any',
    'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor',
    'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can',
    'will', 'just', 'don', 'should', 'now'
  ];
  
  return stopWords.includes(word.toLowerCase());
} 
```

--------------------------------------------------------------------------------
/src/utils/synthesis.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Utilities for synthesizing research results into a coherent report
 */

import { SubQuestion } from '../types.js';

/**
 * Synthesizes research results into a coherent report
 * @param mainQuestion The main research question
 * @param subQuestions Array of sub-questions with their analysis
 * @returns Synthesized research report
 */
export async function synthesizeReport(
  mainQuestion: string,
  subQuestions: SubQuestion[]
): Promise<string> {
  if (!subQuestions || subQuestions.length === 0) {
    return `No research data available for question: "${mainQuestion}"`;
  }

  // Filter completed sub-questions
  const completedSubQuestions = subQuestions.filter(sq => sq.status === 'completed' && sq.analysis);
  
  if (completedSubQuestions.length === 0) {
    return `Research is still in progress for question: "${mainQuestion}"`;
  }
  
  // Generate introduction
  const introduction = generateIntroduction(mainQuestion);
  
  // Generate sections for each sub-question
  const sections = completedSubQuestions.map(sq => {
    return generateSection(sq.question, sq.analysis || '', sq.searchResults || []);
  });
  
  // Generate conclusion
  const conclusion = generateConclusion(mainQuestion, completedSubQuestions);
  
  // Generate sources section
  const sources = generateSources(completedSubQuestions);
  
  // Combine all parts into a complete report
  const report = `
# Research Report: ${mainQuestion}

## Introduction
${introduction}

${sections.join('\n\n')}

## Conclusion
${conclusion}

## Sources
${sources}
`;

  return report;
}

/**
 * Generates the introduction section
 * @param mainQuestion The main research question
 * @returns Introduction text
 */
function generateIntroduction(mainQuestion: string): string {
  return `This report presents a comprehensive analysis of the question: "${mainQuestion}". 
The research was conducted using multiple sources and approaches to provide a thorough understanding of the topic.`;
}

/**
 * Generates a section for a sub-question
 * @param question The sub-question
 * @param analysis The analysis text
 * @param searchResults The search results
 * @returns Formatted section text
 */
function generateSection(
  question: string,
  analysis: string,
  searchResults: { title: string; url: string }[]
): string {
  // Extract a section title from the question
  const sectionTitle = question.replace(/\?/g, '').trim();
  
  // Format the section
  return `## ${sectionTitle}

${analysis}

${generateCitations(analysis, searchResults)}`;
}

/**
 * Generates citation markers for the analysis text
 * @param analysis The analysis text
 * @param searchResults The search results
 * @returns Text with citation markers
 */
function generateCitations(
  analysis: string,
  searchResults: { title: string; url: string }[]
): string {
  // In a real implementation, this would use NLP to match text to sources
  // For the prototype, we'll use a simple approach
  
  if (!searchResults || searchResults.length === 0) {
    return '';
  }
  
  // Create citation notes
  const citations = searchResults.map((result, index) => {
    return `[${index + 1}] ${result.title} - ${result.url}`;
  });
  
  return `**Sources:**\n${citations.join('\n')}`;
}

/**
 * Generates the conclusion section
 * @param mainQuestion The main research question
 * @param subQuestions The sub-questions with analysis
 * @returns Conclusion text
 */
function generateConclusion(
  mainQuestion: string,
  subQuestions: SubQuestion[]
): string {
  return `This research has explored various aspects of ${extractMainTopic(mainQuestion)}. 
The findings from different sub-questions provide a comprehensive understanding of the topic.
The research was based on ${subQuestions.length} sub-questions and utilized multiple sources to ensure accuracy and depth.`;
}

/**
 * Generates the sources section
 * @param subQuestions The sub-questions with search results
 * @returns Formatted sources text
 */
function generateSources(subQuestions: SubQuestion[]): string {
  // Collect all unique sources
  const allSources = new Map<string, { title: string; url: string }>();
  
  subQuestions.forEach(sq => {
    if (sq.searchResults) {
      sq.searchResults.forEach(result => {
        if (!allSources.has(result.url)) {
          allSources.set(result.url, { title: result.title, url: result.url });
        }
      });
    }
  });
  
  // Format the sources
  const sourcesList = Array.from(allSources.values()).map((source, index) => {
    return `${index + 1}. ${source.title} - ${source.url}`;
  });
  
  return sourcesList.join('\n');
}

/**
 * Extracts the main topic from a question
 * @param question The question
 * @returns The main topic
 */
function extractMainTopic(question: string): string {
  // Remove question words at the beginning
  const withoutQuestionWords = question
    .replace(/^(what|who|when|where|why|how|is|are|do|does|did|can|could|would|should|will)\s+/i, '')
    .trim();
  
  // If the question starts with "the", "a", "an", remove the article
  const withoutArticles = withoutQuestionWords
    .replace(/^(the|a|an)\s+/i, '')
    .trim();
  
  // If there's anything left, return it as the main topic
  return withoutArticles.length > 0 ? withoutArticles : question;
} 
```

--------------------------------------------------------------------------------
/src/tools/braveSearch.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * MCP tool for Brave Search
 */

import { MCPTool, MCPToolDefinition, MCPToolResponse } from '../mcp.js';
import { performSearch } from '../utils/search.js';

/**
 * Brave Web Search tool for MCP
 */
export class BraveWebSearchTool implements MCPTool {
  private apiKey: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  /**
   * Get the tool definition
   */
  getDefinition(): MCPToolDefinition {
    return {
      name: 'brave_web_search',
      description: 'Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. Use this for broad information gathering, recent events, or when you need diverse web sources. Supports pagination, content filtering, and freshness controls. Maximum 20 results per request, with offset for pagination. ',
      parameters: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'Search query (max 400 chars, 50 words)'
          },
          count: {
            type: 'number',
            description: 'Number of results (1-20, default 10)',
            default: 10
          },
          offset: {
            type: 'number',
            description: 'Pagination offset (max 9, default 0)',
            default: 0
          }
        },
        required: ['query']
      }
    };
  }

  /**
   * Execute the tool
   * @param params Tool parameters
   * @returns Tool response
   */
  async execute(params: any): Promise<MCPToolResponse> {
    try {
      // Validate parameters
      if (!params.query) {
        return {
          status: 'error',
          error: 'Search query is required'
        };
      }

      // Limit query length
      const query = params.query.slice(0, 400);
      
      // Set count with default and limits
      const count = Math.min(Math.max(params.count || 10, 1), 20);
      
      // Set offset with default and limits
      const offset = Math.min(Math.max(params.offset || 0, 0), 9);

      // Perform the search
      const results = await performSearch(query, this.apiKey, count);

      // Format the response
      return {
        status: 'success',
        result: {
          query,
          count: results.length,
          offset,
          results: results.map(result => ({
            title: result.title,
            description: result.description,
            url: result.url,
            relevance: result.relevance
          }))
        }
      };
    } catch (error) {
      console.error('Error in Brave Web Search tool:', error);
      return {
        status: 'error',
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }
}

/**
 * Brave Local Search tool for MCP
 */
export class BraveLocalSearchTool implements MCPTool {
  private apiKey: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  /**
   * Get the tool definition
   */
  getDefinition(): MCPToolDefinition {
    return {
      name: 'brave_local_search',
      description: 'Searches for local businesses and places using Brave\'s Local Search API. Best for queries related to physical locations, businesses, restaurants, services, etc. Returns detailed information including:\n- Business names and addresses\n- Ratings and review counts\n- Phone numbers and opening hours\nUse this when the query implies \'near me\' or mentions specific locations. Automatically falls back to web search if no local results are found.',
      parameters: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'Local search query (e.g. \'pizza near Central Park\')'
          },
          count: {
            type: 'number',
            description: 'Number of results (1-20, default 5)',
            default: 5
          }
        },
        required: ['query']
      }
    };
  }

  /**
   * Execute the tool
   * @param params Tool parameters
   * @returns Tool response
   */
  async execute(params: any): Promise<MCPToolResponse> {
    try {
      // Validate parameters
      if (!params.query) {
        return {
          status: 'error',
          error: 'Search query is required'
        };
      }

      // Limit query length
      const query = params.query.slice(0, 400);
      
      // Set count with default and limits
      const count = Math.min(Math.max(params.count || 5, 1), 20);

      // For now, we'll use the web search API since we don't have direct access to local search
      // In a real implementation, this would use a different endpoint
      const results = await performSearch(`${query} near me`, this.apiKey, count);

      // Format the response
      return {
        status: 'success',
        result: {
          query,
          count: results.length,
          results: results.map(result => ({
            title: result.title,
            description: result.description,
            url: result.url,
            relevance: result.relevance
          }))
        }
      };
    } catch (error) {
      console.error('Error in Brave Local Search tool:', error);
      return {
        status: 'error',
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/tools/sequentialThinking.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * MCP tool for Sequential Thinking
 */

import { MCPTool, MCPToolDefinition, MCPToolResponse } from '../mcp.js';
import { v4 as uuidv4 } from 'uuid';

/**
 * Sequential Thinking tool for MCP
 */
export class SequentialThinkingTool implements MCPTool {
  private thoughts: Map<string, any[]> = new Map();

  /**
   * Get the tool definition
   */
  getDefinition(): MCPToolDefinition {
    return {
      name: 'sequentialthinking',
      description: 'A detailed tool for dynamic and reflective problem-solving through thoughts.\nThis tool helps analyze problems through a flexible thinking process that can adapt and evolve.\nEach thought can build on, question, or revise previous insights as understanding deepens.\n\nWhen to use this tool:\n- Breaking down complex problems into steps\n- Planning and design with room for revision\n- Analysis that might need course correction\n- Problems where the full scope might not be clear initially\n- Problems that require a multi-step solution\n- Tasks that need to maintain context over multiple steps\n- Situations where irrelevant information needs to be filtered out\n\nKey features:\n- You can adjust total_thoughts up or down as you progress\n- You can question or revise previous thoughts\n- You can add more thoughts even after reaching what seemed like the end\n- You can express uncertainty and explore alternative approaches\n- Not every thought needs to build linearly - you can branch or backtrack\n- Generates a solution hypothesis\n- Verifies the hypothesis based on the Chain of Thought steps\n- Repeats the process until satisfied\n- Provides a correct answer\n\nParameters explained:\n- thought: Your current thinking step, which can include:\n* Regular analytical steps\n* Revisions of previous thoughts\n* Questions about previous decisions\n* Realizations about needing more analysis\n* Changes in approach\n* Hypothesis generation\n* Hypothesis verification\n- next_thought_needed: True if you need more thinking, even if at what seemed like the end\n- thought_number: Current number in sequence (can go beyond initial total if needed)\n- total_thoughts: Current estimate of thoughts needed (can be adjusted up/down)\n- is_revision: A boolean indicating if this thought revises previous thinking\n- revises_thought: If is_revision is true, which thought number is being reconsidered\n- branch_from_thought: If branching, which thought number is the branching point\n- branch_id: Identifier for the current branch (if any)\n- needs_more_thoughts: If reaching end but realizing more thoughts needed\n\nYou should:\n1. Start with an initial estimate of needed thoughts, but be ready to adjust\n2. Feel free to question or revise previous thoughts\n3. Don\'t hesitate to add more thoughts if needed, even at the "end"\n4. Express uncertainty when present\n5. Mark thoughts that revise previous thinking or branch into new paths\n6. Ignore information that is irrelevant to the current step\n7. Generate a solution hypothesis when appropriate\n8. Verify the hypothesis based on the Chain of Thought steps\n9. Repeat the process until satisfied with the solution\n10. Provide a single, ideally correct answer as the final output\n11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached',
      parameters: {
        type: 'object',
        properties: {
          thought: {
            type: 'string',
            description: 'Your current thinking step'
          },
          nextThoughtNeeded: {
            type: 'boolean',
            description: 'Whether another thought step is needed'
          },
          thoughtNumber: {
            type: 'integer',
            description: 'Current thought number',
            minimum: 1
          },
          totalThoughts: {
            type: 'integer',
            description: 'Estimated total thoughts needed',
            minimum: 1
          },
          isRevision: {
            type: 'boolean',
            description: 'Whether this revises previous thinking'
          },
          revisesThought: {
            type: 'integer',
            description: 'Which thought is being reconsidered',
            minimum: 1
          },
          branchFromThought: {
            type: 'integer',
            description: 'Branching point thought number',
            minimum: 1
          },
          branchId: {
            type: 'string',
            description: 'Branch identifier'
          },
          needsMoreThoughts: {
            type: 'boolean',
            description: 'If more thoughts are needed'
          }
        },
        required: ['thought', 'nextThoughtNeeded', 'thoughtNumber', 'totalThoughts']
      }
    };
  }

  /**
   * Execute the tool
   * @param params Tool parameters
   * @returns Tool response
   */
  async execute(params: any): Promise<MCPToolResponse> {
    try {
      // Validate parameters
      if (!params.thought) {
        return {
          status: 'error',
          error: 'Thought content is required'
        };
      }

      if (params.thoughtNumber === undefined || params.thoughtNumber < 1) {
        return {
          status: 'error',
          error: 'Valid thought number is required'
        };
      }

      if (params.totalThoughts === undefined || params.totalThoughts < 1) {
        return {
          status: 'error',
          error: 'Valid total thoughts is required'
        };
      }

      // Generate a session ID if this is the first thought
      let sessionId = '';
      if (params.thoughtNumber === 1) {
        sessionId = uuidv4();
        this.thoughts.set(sessionId, []);
      } else {
        // Find the session ID from previous thoughts
        for (const [id, thoughts] of this.thoughts.entries()) {
          if (thoughts.length > 0 && thoughts.some(t => t.thoughtNumber === params.thoughtNumber - 1)) {
            sessionId = id;
            break;
          }
        }

        if (!sessionId) {
          // If we can't find a previous session, create a new one
          sessionId = uuidv4();
          this.thoughts.set(sessionId, []);
        }
      }

      // Store the thought
      const thought = {
        thoughtNumber: params.thoughtNumber,
        totalThoughts: params.totalThoughts,
        content: params.thought,
        nextThoughtNeeded: params.nextThoughtNeeded,
        isRevision: params.isRevision || false,
        revisesThought: params.revisesThought,
        branchFromThought: params.branchFromThought,
        branchId: params.branchId,
        needsMoreThoughts: params.needsMoreThoughts || false,
        timestamp: Date.now()
      };

      const thoughts = this.thoughts.get(sessionId) || [];
      thoughts.push(thought);
      this.thoughts.set(sessionId, thoughts);

      // Format the response
      return {
        status: 'success',
        result: {
          sessionId,
          thoughtNumber: params.thoughtNumber,
          totalThoughts: params.totalThoughts,
          nextThoughtNeeded: params.nextThoughtNeeded,
          previousThoughts: thoughts
            .filter(t => t.thoughtNumber < params.thoughtNumber)
            .map(t => ({
              thoughtNumber: t.thoughtNumber,
              content: t.content,
              isRevision: t.isRevision,
              revisesThought: t.revisesThought
            }))
        }
      };
    } catch (error) {
      console.error('Error in Sequential Thinking tool:', error);
      return {
        status: 'error',
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/tools/deepResearch.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * MCP tool for Deep Research
 * Combines Sequential Thinking and Brave Search for comprehensive research
 */

import { MCPTool, MCPToolDefinition, MCPToolResponse } from '../mcp.js';
import { v4 as uuidv4 } from 'uuid';
import { analyzeQuestion } from '../utils/question.js';
import { performSearch } from '../utils/search.js';
import { analyzeResults } from '../utils/analysis.js';
import { synthesizeReport } from '../utils/synthesis.js';
import { ResearchData, ResearchStatus, ResearchStep, ResearchStepType, SubQuestion } from '../types.js';

/**
 * Deep Research tool for MCP
 */
export class DeepResearchTool implements MCPTool {
  private apiKey: string;
  private researches: Map<string, ResearchData> = new Map();
  
  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  /**
   * Get the tool definition
   */
  getDefinition(): MCPToolDefinition {
    return {
      name: 'deep_research',
      description: 'Performs comprehensive, in-depth research on complex topics. Combines Sequential Thinking with Brave Search to provide detailed, well-sourced research reports. Ideal for academic research, complex questions, and topics requiring synthesis of multiple sources.',
      parameters: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'The research question or topic'
          },
          maxSubQuestions: {
            type: 'number',
            description: 'Maximum number of sub-questions to generate (default: 5)',
            default: 5
          },
          maxSearchesPerQuestion: {
            type: 'number',
            description: 'Maximum number of searches per sub-question (default: 2)',
            default: 2
          },
          researchId: {
            type: 'string',
            description: 'ID of an existing research to continue or retrieve'
          },
          action: {
            type: 'string',
            description: 'Action to perform: "start", "status", "continue", or "report"',
            default: 'start'
          }
        },
        required: ['query']
      }
    };
  }

  /**
   * Execute the tool
   * @param params Tool parameters
   * @returns Tool response
   */
  async execute(params: any): Promise<MCPToolResponse> {
    try {
      // Validate parameters
      if (!params.query && !params.researchId) {
        return {
          status: 'error',
          error: 'Either query or researchId is required'
        };
      }

      // Determine the action
      const action = params.action || 'start';
      
      // Handle different actions
      switch (action) {
        case 'start':
          return this.startResearch(params);
        case 'status':
          return this.getResearchStatus(params);
        case 'continue':
          return this.continueResearch(params);
        case 'report':
          return this.generateReport(params);
        default:
          return {
            status: 'error',
            error: `Invalid action: ${action}`
          };
      }
    } catch (error) {
      console.error('Error in Deep Research tool:', error);
      return {
        status: 'error',
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  /**
   * Start a new research
   * @param params Tool parameters
   * @returns Tool response
   */
  private async startResearch(params: any): Promise<MCPToolResponse> {
    // Generate a research ID
    const researchId = uuidv4();
    
    // Create a new research data object
    const research: ResearchData = {
      id: researchId,
      question: params.query,
      subQuestions: [],
      steps: [],
      status: ResearchStatus.PLANNING,
      startTime: Date.now()
    };
    
    // Store the research
    this.researches.set(researchId, research);
    
    // Add the first step - question analysis
    const step: ResearchStep = {
      id: uuidv4(),
      type: ResearchStepType.QUESTION_ANALYSIS,
      content: `Analyzing question: "${params.query}"`,
      timestamp: Date.now()
    };
    
    research.steps.push(step);
    
    // Analyze the question to generate sub-questions
    const subQuestions = await analyzeQuestion(params.query);
    
    // Limit the number of sub-questions
    const maxSubQuestions = Math.min(params.maxSubQuestions || 5, 10);
    const limitedSubQuestions = subQuestions.slice(0, maxSubQuestions);
    
    // Create sub-question objects
    research.subQuestions = limitedSubQuestions.map(question => ({
      id: uuidv4(),
      question,
      status: 'pending'
    }));
    
    // Update the step with the sub-questions
    step.content = `Analyzed question: "${params.query}"\nGenerated ${research.subQuestions.length} sub-questions:\n${research.subQuestions.map(sq => `- ${sq.question}`).join('\n')}`;
    
    // Update the research status
    research.status = ResearchStatus.SEARCHING;
    
    // Return the response
    return {
      status: 'success',
      result: {
        researchId,
        question: params.query,
        subQuestions: research.subQuestions.map(sq => sq.question),
        status: research.status
      }
    };
  }

  /**
   * Get the status of a research
   * @param params Tool parameters
   * @returns Tool response
   */
  private getResearchStatus(params: any): Promise<MCPToolResponse> {
    // Get the research ID
    const researchId = params.researchId;
    
    // Check if the research exists
    if (!researchId || !this.researches.has(researchId)) {
      return Promise.resolve({
        status: 'error',
        error: `Research not found: ${researchId}`
      });
    }
    
    // Get the research
    const research = this.researches.get(researchId)!;
    
    // Return the status
    return Promise.resolve({
      status: 'success',
      result: {
        researchId,
        question: research.question,
        status: research.status,
        subQuestions: research.subQuestions.map(sq => ({
          question: sq.question,
          status: sq.status
        })),
        steps: research.steps.length,
        startTime: research.startTime,
        endTime: research.endTime
      }
    });
  }

  /**
   * Continue a research
   * @param params Tool parameters
   * @returns Tool response
   */
  private async continueResearch(params: any): Promise<MCPToolResponse> {
    // Get the research ID
    const researchId = params.researchId;
    
    // Check if the research exists
    if (!researchId || !this.researches.has(researchId)) {
      return {
        status: 'error',
        error: `Research not found: ${researchId}`
      };
    }
    
    // Get the research
    const research = this.researches.get(researchId)!;
    
    // Check if the research is already completed
    if (research.status === ResearchStatus.COMPLETED) {
      return {
        status: 'success',
        result: {
          researchId,
          question: research.question,
          status: research.status,
          message: 'Research is already completed'
        }
      };
    }
    
    // Find the next pending sub-question
    const nextSubQuestion = research.subQuestions.find(sq => sq.status === 'pending');
    
    // If there are no more pending sub-questions, synthesize the results
    if (!nextSubQuestion) {
      return this.synthesizeResults(research);
    }
    
    // Mark the sub-question as in-progress
    nextSubQuestion.status = 'in-progress';
    
    // Add a search step
    const searchStep: ResearchStep = {
      id: uuidv4(),
      type: ResearchStepType.SEARCH,
      content: `Searching for: "${nextSubQuestion.question}"`,
      timestamp: Date.now()
    };
    
    research.steps.push(searchStep);
    
    // Perform the search
    const searchResults = await performSearch(nextSubQuestion.question, this.apiKey, 10);
    
    // Store the search results
    nextSubQuestion.searchResults = searchResults;
    
    // Update the search step
    searchStep.content = `Searched for: "${nextSubQuestion.question}"\nFound ${searchResults.length} results`;
    
    // Add an analysis step
    const analysisStep: ResearchStep = {
      id: uuidv4(),
      type: ResearchStepType.RESULT_ANALYSIS,
      content: `Analyzing results for: "${nextSubQuestion.question}"`,
      timestamp: Date.now()
    };
    
    research.steps.push(analysisStep);
    
    // Analyze the results
    const analysis = await analyzeResults(searchResults, nextSubQuestion.question);
    
    // Store the analysis
    nextSubQuestion.analysis = analysis;
    
    // Update the analysis step
    analysisStep.content = `Analyzed results for: "${nextSubQuestion.question}"`;
    
    // Mark the sub-question as completed
    nextSubQuestion.status = 'completed';
    
    // Return the response
    return {
      status: 'success',
      result: {
        researchId,
        question: research.question,
        subQuestionCompleted: nextSubQuestion.question,
        remainingSubQuestions: research.subQuestions.filter(sq => sq.status === 'pending').length,
        status: research.status
      }
    };
  }

  /**
   * Synthesize the results of a research
   * @param research The research data
   * @returns Tool response
   */
  private async synthesizeResults(research: ResearchData): Promise<MCPToolResponse> {
    // Add a synthesis step
    const synthesisStep: ResearchStep = {
      id: uuidv4(),
      type: ResearchStepType.SYNTHESIS,
      content: 'Synthesizing research results',
      timestamp: Date.now()
    };
    
    research.steps.push(synthesisStep);
    
    // Update the research status
    research.status = ResearchStatus.SYNTHESIZING;
    
    // Synthesize the report
    const report = await synthesizeReport(research.question, research.subQuestions);
    
    // Store the report
    research.report = report;
    
    // Update the synthesis step
    synthesisStep.content = 'Synthesized research results';
    
    // Update the research status and end time
    research.status = ResearchStatus.COMPLETED;
    research.endTime = Date.now();
    
    // Return the response
    return {
      status: 'success',
      result: {
        researchId: research.id,
        question: research.question,
        status: research.status,
        message: 'Research completed',
        reportPreview: report.substring(0, 500) + '...'
      }
    };
  }

  /**
   * Generate a report for a research
   * @param params Tool parameters
   * @returns Tool response
   */
  private generateReport(params: any): Promise<MCPToolResponse> {
    // Get the research ID
    const researchId = params.researchId;
    
    // Check if the research exists
    if (!researchId || !this.researches.has(researchId)) {
      return Promise.resolve({
        status: 'error',
        error: `Research not found: ${researchId}`
      });
    }
    
    // Get the research
    const research = this.researches.get(researchId)!;
    
    // Check if the research is completed
    if (research.status !== ResearchStatus.COMPLETED) {
      return Promise.resolve({
        status: 'error',
        error: 'Research is not yet completed'
      });
    }
    
    // Return the report
    return Promise.resolve({
      status: 'success',
      result: {
        researchId,
        question: research.question,
        report: research.report,
        subQuestions: research.subQuestions.length,
        steps: research.steps.length,
        startTime: research.startTime,
        endTime: research.endTime,
        duration: (research.endTime! - research.startTime) / 1000
      }
    });
  }
} 
```