#
tokens: 17198/50000 14/14 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | 
 4 | # Build
 5 | dist/
 6 | 
 7 | # Environment variables
 8 | .env
 9 | 
10 | # Logs
11 | logs
12 | *.log
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | 
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | 
26 | # OS
27 | .DS_Store
28 | Thumbs.db 
29 | 
30 | # Thrash
31 | /downloads
```

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

```markdown
  1 | # OpenDeepSearch
  2 | 
  3 | An open-source alternative to Perplexity Deep Research using the Model Context Protocol (MCP).
  4 | 
  5 | ## Overview
  6 | 
  7 | 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.
  8 | 
  9 | ## Features
 10 | 
 11 | - **Comprehensive Research**: Breaks down complex questions into manageable sub-questions
 12 | - **Iterative Search**: Performs multiple searches to gather diverse information
 13 | - **Intelligent Analysis**: Analyzes search results to extract relevant information
 14 | - **Synthesis**: Combines findings into a coherent, well-structured report
 15 | - **Citations**: Includes sources for all information in the report
 16 | - **MCP Integration**: Seamlessly integrates with Claude Desktop, Cursor, and other MCP clients
 17 | - **WebSockets**: Supports integration with Smithery and other MCP clients
 18 | - **Publication**: Allows publishing the research tool on the Smithery platform for easy access
 19 | 
 20 | ## Installation
 21 | 
 22 | ### Prerequisites
 23 | 
 24 | - Node.js 16 or higher
 25 | - A Brave Search API key (get one at [https://brave.com/search/api/](https://brave.com/search/api/))
 26 | 
 27 | ### NPM Installation
 28 | 
 29 | ```bash
 30 | npm install -g open-deep-research
 31 | ```
 32 | 
 33 | ### Running with NPX
 34 | 
 35 | ```bash
 36 | BRAVE_API_KEY=your_api_key npx open-deep-research
 37 | ```
 38 | 
 39 | ### Local Installation
 40 | 
 41 | ```bash
 42 | # Clone the repository
 43 | git clone https://github.com/tositon/open-deep-research.git
 44 | cd open-deep-research
 45 | 
 46 | # Install dependencies
 47 | npm install
 48 | 
 49 | # Build the project
 50 | npm run build
 51 | 
 52 | # Run with Brave Search API
 53 | BRAVE_API_KEY=your_api_key npm start
 54 | ```
 55 | 
 56 | ### Installation via Smithery
 57 | 
 58 | ```bash
 59 | # Install for Claude
 60 | npx @smithery/cli install open-deep-research --client claude
 61 | 
 62 | # Install for Cursor
 63 | npx @smithery/cli install open-deep-research --client cursor
 64 | ```
 65 | 
 66 | When installing via Smithery, you will be prompted to enter a Brave Search API key.
 67 | 
 68 | ## Usage
 69 | 
 70 | ### With Claude Desktop
 71 | 
 72 | Add the following to your Claude Desktop configuration:
 73 | 
 74 | ```json
 75 | {
 76 |   "mcpServers": {
 77 |     "open-deep-research": {
 78 |       "command": "npx",
 79 |       "args": [
 80 |         "-y",
 81 |         "open-deep-research"
 82 |       ],
 83 |       "env": {
 84 |         "BRAVE_API_KEY": "your_api_key_here"
 85 |       }
 86 |     }
 87 |   }
 88 | }
 89 | ```
 90 | 
 91 | ### With Cursor
 92 | 
 93 | In Cursor, you can add the MCP server with:
 94 | 
 95 | ```
 96 | claude mcp add "open-deep-research" npx open-deep-research
 97 | ```
 98 | 
 99 | Make sure to set the `BRAVE_API_KEY` environment variable before running Cursor.
100 | 
101 | ### Example Queries
102 | 
103 | - "What are the latest developments in quantum computing?"
104 | - "Compare and contrast different approaches to climate change mitigation"
105 | - "Explain the history and impact of the Renaissance on European art"
106 | - "What are the pros and cons of different renewable energy sources?"
107 | 
108 | ## How It Works
109 | 
110 | 1. **Question Analysis**: The system analyzes the main question and breaks it down into sub-questions
111 | 2. **Iterative Search**: For each sub-question, the system performs searches using Brave Search API
112 | 3. **Result Analysis**: The system analyzes the search results to extract relevant information
113 | 4. **Synthesis**: The system combines the findings into a coherent report
114 | 5. **Citation**: All information is properly cited with sources
115 | 
116 | ## Development
117 | 
118 | ### Setup
119 | 
120 | ```bash
121 | git clone https://github.com/tositon/open-deep-research.git
122 | cd open-deep-research
123 | npm install
124 | ```
125 | 
126 | ### Build
127 | 
128 | ```bash
129 | npm run build
130 | ```
131 | 
132 | ### Run in Development Mode
133 | 
134 | ```bash
135 | BRAVE_API_KEY=your_api_key npm run dev
136 | ```
137 | 
138 | ## Testing
139 | 
140 | ### Testing with MCP Inspector
141 | 
142 | Для тестирования MCP сервера можно использовать MCP Inspector, который предоставляет удобный интерфейс для взаимодействия с инструментами:
143 | 
144 | ```bash
145 | # Установка и запуск MCP Inspector
146 | npx @modelcontextprotocol/inspector
147 | 
148 | # Запуск сервера в другом терминале
149 | BRAVE_API_KEY=your_api_key npm start
150 | ```
151 | 
152 | После запуска Inspector, откройте браузер и перейдите по адресу http://localhost:5173. Подключитесь к WebSocket серверу, используя URL `ws://localhost:3000`.
153 | 
154 | ### Примеры запросов для тестирования инструментов
155 | 
156 | В интерфейсе MCP Inspector вы можете выбрать инструмент и настроить параметры запроса:
157 | 
158 | #### Тестирование Brave Web Search
159 | 
160 | ```json
161 | {
162 |   "query": "latest quantum computing advancements",
163 |   "count": 5
164 | }
165 | ```
166 | 
167 | #### Тестирование Sequential Thinking
168 | 
169 | ```json
170 | {
171 |   "thought": "Начинаю анализ проблемы глобального потепления",
172 |   "thoughtNumber": 1,
173 |   "totalThoughts": 5,
174 |   "nextThoughtNeeded": true
175 | }
176 | ```
177 | 
178 | #### Тестирование Deep Research
179 | 
180 | ```json
181 | {
182 |   "query": "Сравнение различных источников возобновляемой энергии",
183 |   "action": "start",
184 |   "maxSubQuestions": 3
185 | }
186 | ```
187 | 
188 | ### Testing with Claude or Cursor
189 | 
190 | После установки сервера через Smithery или локально, вы можете использовать его с Claude Desktop или Cursor, выбрав соответствующий MCP сервер в настройках.
191 | 
192 | ## Publishing on Smithery
193 | 
194 | To publish the server on the Smithery platform:
195 | 
196 | 1. Ensure the repository is hosted on GitHub and is public
197 | 2. Register on the [Smithery](https://smithery.ai/) platform
198 | 3. Authenticate via GitHub to connect with the repository
199 | 4. Go to the "Deployments" tab on the server page
200 | 5. Click the "Deploy on Smithery" button
201 | 6. Follow the deployment setup instructions
202 | 
203 | After publishing, users can install the server using the Smithery CLI:
204 | 
205 | ```bash
206 | npx @smithery/cli install open-deep-research --client claude
207 | ```
208 | 
209 | ## Contributing
210 | 
211 | Contributions are welcome! Please feel free to submit a Pull Request.
212 | 
213 | ## License
214 | 
215 | This project is licensed under the MIT License - see the LICENSE file for details.
216 | 
217 | ## Acknowledgments
218 | 
219 | - Inspired by Perplexity Deep Research
220 | - Built on the Model Context Protocol
221 | - Uses Sequential Thinking approach for structured research
222 | - Powered by Brave Search API 
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "strict": true,
 8 |     "outDir": "dist",
 9 |     "declaration": true,
10 |     "sourceMap": true,
11 |     "skipLibCheck": true,
12 |     "forceConsistentCasingInFileNames": true
13 |   },
14 |   "include": ["src/**/*"],
15 |   "exclude": ["node_modules", "dist"]
16 | } 
```

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

```typescript
 1 | declare module '@modelcontextprotocol/sdk/server/mcp.js' {
 2 |   export class McpServer {
 3 |     constructor(options: {
 4 |       name: string;
 5 |       version: string;
 6 |       description: string;
 7 |     });
 8 | 
 9 |     tool(
10 |       name: string, 
11 |       description: string,
12 |       paramsSchema: any,
13 |       handler: (params: any) => Promise<any>
14 |     ): void;
15 | 
16 |     connect(transport: any): Promise<void>;
17 |   }
18 | } 
```

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

```json
 1 | {
 2 |   "name": "open-deep-research",
 3 |   "version": "0.1.0",
 4 |   "description": "An open-source alternative to Perplexity Deep Research using MCP protocol",
 5 |   "main": "dist/index.js",
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "open-deep-research": "dist/index.js"
 9 |   },
10 |   "scripts": {
11 |     "build": "tsc",
12 |     "start": "node dist/index.js",
13 |     "dev": "tsx src/index.ts",
14 |     "lint": "eslint src --ext .ts",
15 |     "format": "prettier --write \"src/**/*.ts\"",
16 |     "prepublishOnly": "npm run build",
17 |     "test": "./test-server.sh",
18 |     "clean": "rm -rf dist",
19 |     "rebuild": "npm run clean && npm run build"
20 |   },
21 |   "keywords": [
22 |     "mcp",
23 |     "deep-research",
24 |     "perplexity",
25 |     "ai",
26 |     "search",
27 |     "research",
28 |     "cursor"
29 |   ],
30 |   "author": "",
31 |   "license": "MIT",
32 |   "dependencies": {
33 |     "@modelcontextprotocol/sdk": "^1.7.0",
34 |     "axios": "^1.8.3",
35 |     "chalk": "^5.4.1",
36 |     "dotenv": "^16.4.7",
37 |     "uuid": "^9.0.1",
38 |     "ws": "^8.18.1"
39 |   },
40 |   "devDependencies": {
41 |     "@types/node": "^20.10.0",
42 |     "@types/uuid": "^9.0.7",
43 |     "@types/ws": "^8.18.0",
44 |     "eslint": "^8.54.0",
45 |     "prettier": "^3.1.0",
46 |     "tsx": "^4.6.0",
47 |     "typescript": "^5.3.2"
48 |   },
49 |   "engines": {
50 |     "node": ">=16.0.0"
51 |   },
52 |   "repository": {
53 |     "type": "git",
54 |     "url": "https://github.com/tositon/OpenDeepSearch.git"
55 |   },
56 |   "bugs": {
57 |     "url": "https://github.com/tositon/OpenDeepSearch/issues"
58 |   },
59 |   "homepage": "https://github.com/tositon/OpenDeepSearch#readme"
60 | }
61 | 
```

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

```typescript
 1 | /**
 2 |  * Types and interfaces for OpenDeepSearch
 3 |  */
 4 | 
 5 | // Type of research step
 6 | export enum ResearchStepType {
 7 |   QUESTION_ANALYSIS = 'question_analysis',
 8 |   SEARCH = 'search',
 9 |   RESULT_ANALYSIS = 'result_analysis',
10 |   SYNTHESIS = 'synthesis',
11 |   FOLLOW_UP = 'follow_up'
12 | }
13 | 
14 | // Status of research
15 | export enum ResearchStatus {
16 |   PLANNING = 'planning',
17 |   SEARCHING = 'searching',
18 |   ANALYZING = 'analyzing',
19 |   SYNTHESIZING = 'synthesizing',
20 |   COMPLETED = 'completed'
21 | }
22 | 
23 | // Data for one research step
24 | export interface ResearchStep {
25 |   id: string;
26 |   type: ResearchStepType;
27 |   content: string;
28 |   timestamp: number;
29 |   metadata?: Record<string, any>;
30 | }
31 | 
32 | // Sub-question
33 | export interface SubQuestion {
34 |   id: string;
35 |   question: string;
36 |   status: 'pending' | 'in-progress' | 'completed';
37 |   searchResults?: SearchResult[];
38 |   analysis?: string;
39 | }
40 | 
41 | // Search result
42 | export interface SearchResult {
43 |   title: string;
44 |   description: string;
45 |   url: string;
46 |   relevance?: number; // Relevance score from 0 to 1
47 | }
48 | 
49 | // Complete research data
50 | export interface ResearchData {
51 |   id: string;
52 |   question: string;
53 |   subQuestions: SubQuestion[];
54 |   steps: ResearchStep[];
55 |   status: ResearchStatus;
56 |   report?: string;
57 |   startTime: number;
58 |   endTime?: number;
59 | }
60 | 
61 | // Options for the server
62 | export interface DeepResearchOptions {
63 |   braveApiKey: string;
64 |   maxSubQuestions?: number; // Maximum number of sub-questions
65 |   maxSearchesPerQuestion?: number; // Maximum number of searches per sub-question
66 |   maxTotalSteps?: number; // Maximum number of research steps
67 |   timeout?: number; // Timeout in milliseconds
68 | } 
```

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

```typescript
 1 | /**
 2 |  * Type definitions for Model Context Protocol (MCP)
 3 |  * Since we don't have access to the official SDK, we'll define our own types
 4 |  */
 5 | 
 6 | /**
 7 |  * MCP Tool Definition
 8 |  */
 9 | export interface MCPToolDefinition {
10 |   name: string;
11 |   description: string;
12 |   parameters: {
13 |     type: string;
14 |     properties: Record<string, any>;
15 |     required?: string[];
16 |   };
17 | }
18 | 
19 | /**
20 |  * MCP Tool Response
21 |  */
22 | export interface MCPToolResponse {
23 |   status: 'success' | 'error';
24 |   result?: any;
25 |   error?: string;
26 | }
27 | 
28 | /**
29 |  * MCP Tool Interface
30 |  */
31 | export interface MCPTool {
32 |   getDefinition(): MCPToolDefinition;
33 |   execute(params: any): Promise<MCPToolResponse>;
34 | }
35 | 
36 | /**
37 |  * MCP Server Options
38 |  */
39 | export interface MCPServerOptions {
40 |   name: string;
41 |   version: string;
42 |   description: string;
43 | }
44 | 
45 | /**
46 |  * MCP Server
47 |  */
48 | export class MCPServer {
49 |   private options: MCPServerOptions;
50 |   private tools: Map<string, MCPTool> = new Map();
51 | 
52 |   constructor(options: MCPServerOptions) {
53 |     this.options = options;
54 |   }
55 | 
56 |   /**
57 |    * Register a tool with the server
58 |    * @param tool The tool to register
59 |    */
60 |   registerTool(tool: MCPTool): void {
61 |     const definition = tool.getDefinition();
62 |     this.tools.set(definition.name, tool);
63 |   }
64 | 
65 |   /**
66 |    * Start the MCP server
67 |    * This is a simplified implementation that doesn't actually start a server
68 |    * In a real implementation, this would start a WebSocket server
69 |    */
70 |   async start(): Promise<void> {
71 |     // In a real implementation, this would start a WebSocket server
72 |     // For now, we'll just log that the server is starting
73 |     console.log(`Starting MCP server: ${this.options.name} v${this.options.version}`);
74 |     console.log(`Description: ${this.options.description}`);
75 |     console.log(`Registered tools: ${Array.from(this.tools.keys()).join(', ')}`);
76 |   }
77 | 
78 |   /**
79 |    * Stop the MCP server
80 |    */
81 |   async stop(): Promise<void> {
82 |     // In a real implementation, this would stop the WebSocket server
83 |     console.log(`Stopping MCP server: ${this.options.name}`);
84 |   }
85 | } 
```

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

```typescript
 1 | /**
 2 |  * Utilities for working with questions
 3 |  */
 4 | 
 5 | // Если в будущем будут добавлены импорты, они должны включать расширение .js
 6 | // import { SomeType } from '../types.js';
 7 | 
 8 | /**
 9 |  * Analyzes the main question and breaks it down into sub-questions
10 |  * @param question The main question
11 |  * @returns Array of sub-questions
12 |  */
13 | export async function analyzeQuestion(question: string): Promise<string[]> {
14 |   // In a real implementation, this could use an LLM for question decomposition
15 |   // For the prototype, we'll use a simple algorithm
16 |   
17 |   // Remove question marks and split by "and", "or", commas
18 |   const cleanQuestion = question.replace(/\?/g, '').trim();
19 |   
20 |   // Look for keywords that might indicate a compound question
21 |   const conjunctions = ['and', 'or', 'versus', 'vs', 'compared to', 'differences between'];
22 |   let hasConjunction = false;
23 |   
24 |   for (const conj of conjunctions) {
25 |     if (cleanQuestion.toLowerCase().includes(conj)) {
26 |       hasConjunction = true;
27 |       break;
28 |     }
29 |   }
30 |   
31 |   // If the question contains conjunctions, break it down
32 |   if (hasConjunction) {
33 |     // Simple heuristic for breaking down the question
34 |     // In a real implementation, this would be more sophisticated
35 |     const parts = cleanQuestion.split(/\s+(?:and|or|versus|vs|compared to)\s+/i);
36 |     
37 |     if (parts.length > 1) {
38 |       // Form sub-questions based on parts
39 |       return parts.map(part => `${part.trim()}?`);
40 |     }
41 |   }
42 |   
43 |   // If we couldn't break it down by conjunctions, create sub-questions by key aspects
44 |   // This is a simplified version, a real implementation would need a more complex algorithm
45 |   const aspects = [
46 |     'what is', 'how does', 'why is', 'when was', 'where is',
47 |     'definition', 'history', 'examples', 'advantages', 'disadvantages'
48 |   ];
49 |   
50 |   const subQuestions = [];
51 |   
52 |   // Add the main question
53 |   subQuestions.push(question);
54 |   
55 |   // Add sub-questions by aspects
56 |   const mainTopic = extractMainTopic(cleanQuestion);
57 |   if (mainTopic) {
58 |     // Add several sub-questions on different aspects
59 |     subQuestions.push(`What is ${mainTopic}?`);
60 |     subQuestions.push(`What are the key features of ${mainTopic}?`);
61 |     subQuestions.push(`What are the applications of ${mainTopic}?`);
62 |   }
63 |   
64 |   // Remove duplicates and return unique sub-questions
65 |   return Array.from(new Set(subQuestions));
66 | }
67 | 
68 | /**
69 |  * Extracts the main topic from a question
70 |  * @param question The question
71 |  * @returns The main topic or null if it couldn't be determined
72 |  */
73 | function extractMainTopic(question: string): string | null {
74 |   // Remove question words at the beginning
75 |   const withoutQuestionWords = question
76 |     .replace(/^(what|who|when|where|why|how|is|are|do|does|did|can|could|would|should|will)\s+/i, '')
77 |     .trim();
78 |   
79 |   // If the question starts with "the", "a", "an", remove the article
80 |   const withoutArticles = withoutQuestionWords
81 |     .replace(/^(the|a|an)\s+/i, '')
82 |     .trim();
83 |   
84 |   // If there's anything left, return it as the main topic
85 |   return withoutArticles.length > 0 ? withoutArticles : null;
86 | } 
```

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

```typescript
  1 | /**
  2 |  * Utilities for performing searches using Brave Search API
  3 |  */
  4 | 
  5 | import axios from 'axios';
  6 | import { SearchResult } from '../types.js';
  7 | 
  8 | /**
  9 |  * Performs a search using the Brave Search API
 10 |  * @param query The search query
 11 |  * @param apiKey The Brave Search API key
 12 |  * @param count The number of results to return (max 20)
 13 |  * @returns Array of search results
 14 |  */
 15 | export async function performSearch(
 16 |   query: string,
 17 |   apiKey: string,
 18 |   count: number = 10
 19 | ): Promise<SearchResult[]> {
 20 |   if (!query) {
 21 |     throw new Error('Search query is required');
 22 |   }
 23 | 
 24 |   if (!apiKey) {
 25 |     throw new Error('Brave Search API key is required');
 26 |   }
 27 | 
 28 |   // Limit count to maximum of 20 results
 29 |   const limitedCount = Math.min(count, 20);
 30 | 
 31 |   try {
 32 |     // Construct the API request URL
 33 |     const url = `https://api.search.brave.com/res/v1/web/search`;
 34 |     
 35 |     // Make the API request
 36 |     const response = await axios.get(url, {
 37 |       headers: {
 38 |         'Accept': 'application/json',
 39 |         'Accept-Encoding': 'gzip',
 40 |         'X-Subscription-Token': apiKey
 41 |       },
 42 |       params: {
 43 |         q: query,
 44 |         count: limitedCount
 45 |       }
 46 |     });
 47 | 
 48 |     // Check if the response is valid
 49 |     if (!response.data || !response.data.web || !response.data.web.results) {
 50 |       return [];
 51 |     }
 52 | 
 53 |     // Parse the results
 54 |     const results: SearchResult[] = response.data.web.results.map((result: any) => {
 55 |       return {
 56 |         title: result.title || '',
 57 |         url: result.url || '',
 58 |         description: result.description || '',
 59 |         relevance: calculateRelevance(query, result.title, result.description)
 60 |       };
 61 |     });
 62 | 
 63 |     return results;
 64 |   } catch (error) {
 65 |     console.error('Error performing search:', error);
 66 |     throw new Error(`Failed to perform search: ${error instanceof Error ? error.message : String(error)}`);
 67 |   }
 68 | }
 69 | 
 70 | /**
 71 |  * Calculates the relevance score of a search result
 72 |  * @param query The search query
 73 |  * @param title The result title
 74 |  * @param description The result description
 75 |  * @returns A relevance score between 0 and 1
 76 |  */
 77 | function calculateRelevance(query: string, title: string, description: string): number {
 78 |   // Normalize the query and result text
 79 |   const normalizedQuery = query.toLowerCase();
 80 |   const normalizedTitle = title.toLowerCase();
 81 |   const normalizedDescription = description.toLowerCase();
 82 |   
 83 |   // Split the query into words
 84 |   const queryWords = normalizedQuery.split(/\s+/).filter(word => word.length > 2);
 85 |   
 86 |   // Count matches in title (weighted higher)
 87 |   let titleMatches = 0;
 88 |   for (const word of queryWords) {
 89 |     if (normalizedTitle.includes(word)) {
 90 |       titleMatches++;
 91 |     }
 92 |   }
 93 |   
 94 |   // Count matches in description
 95 |   let descriptionMatches = 0;
 96 |   for (const word of queryWords) {
 97 |     if (normalizedDescription.includes(word)) {
 98 |       descriptionMatches++;
 99 |     }
100 |   }
101 |   
102 |   // Calculate relevance score (title matches weighted 3x)
103 |   const maxPossibleScore = queryWords.length * 4; // 3 for title + 1 for description
104 |   const actualScore = (titleMatches * 3) + descriptionMatches;
105 |   
106 |   // Return normalized score between 0 and 1
107 |   return maxPossibleScore > 0 ? actualScore / maxPossibleScore : 0;
108 | } 
```

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

```typescript
  1 | /**
  2 |  * Utilities for analyzing search results
  3 |  */
  4 | 
  5 | import { SearchResult } from '../types.js';
  6 | 
  7 | /**
  8 |  * Analyzes search results and extracts key information
  9 |  * @param results Array of search results
 10 |  * @param query The original search query
 11 |  * @returns Analysis report
 12 |  */
 13 | export async function analyzeResults(
 14 |   results: SearchResult[],
 15 |   query: string
 16 | ): Promise<string> {
 17 |   if (!results || results.length === 0) {
 18 |     return `No results found for query: "${query}"`;
 19 |   }
 20 | 
 21 |   // Sort results by relevance
 22 |   const sortedResults = [...results].sort((a, b) => {
 23 |     const relevanceA = a.relevance ?? 0;
 24 |     const relevanceB = b.relevance ?? 0;
 25 |     return relevanceB - relevanceA;
 26 |   });
 27 |   
 28 |   // Take the top 5 most relevant results
 29 |   const topResults = sortedResults.slice(0, 5);
 30 |   
 31 |   // Extract key information from each result
 32 |   const analysisPoints = topResults.map((result, index) => {
 33 |     const keyInfo = extractKeyInformation(result, query);
 34 |     return `${index + 1}. ${result.title}\n   ${keyInfo}\n   Source: ${result.url}`;
 35 |   });
 36 |   
 37 |   // Format the analysis report
 38 |   const report = `
 39 | Analysis for query: "${query}"
 40 | 
 41 | Key Information:
 42 | ${analysisPoints.join('\n\n')}
 43 | 
 44 | This analysis is based on the top ${topResults.length} most relevant results.
 45 | `;
 46 | 
 47 |   return report;
 48 | }
 49 | 
 50 | /**
 51 |  * Extracts key information from a search result
 52 |  * @param result The search result
 53 |  * @param query The original search query
 54 |  * @returns Extracted key information
 55 |  */
 56 | function extractKeyInformation(result: SearchResult, query: string): string {
 57 |   // In a real implementation, this would use NLP techniques
 58 |   // For the prototype, we'll use a simple approach
 59 |   
 60 |   // Get the most relevant sentences from the description
 61 |   const relevantSentences = selectRelevantSentences(result.description, query, 2);
 62 |   
 63 |   return relevantSentences.join(' ');
 64 | }
 65 | 
 66 | /**
 67 |  * Selects the most relevant sentences from a text
 68 |  * @param text The text to analyze
 69 |  * @param query The search query
 70 |  * @param maxSentences Maximum number of sentences to return
 71 |  * @returns Array of relevant sentences
 72 |  */
 73 | function selectRelevantSentences(text: string, query: string, maxSentences: number): string[] {
 74 |   // Split text into sentences
 75 |   const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
 76 |   
 77 |   if (sentences.length === 0) {
 78 |     return [text];
 79 |   }
 80 |   
 81 |   // Calculate relevance score for each sentence
 82 |   const scoredSentences = sentences.map(sentence => {
 83 |     const score = calculateSentenceRelevance(sentence, query);
 84 |     return { sentence, score };
 85 |   });
 86 |   
 87 |   // Sort by relevance score
 88 |   scoredSentences.sort((a, b) => b.score - a.score);
 89 |   
 90 |   // Return the top N sentences
 91 |   return scoredSentences.slice(0, maxSentences).map(s => s.sentence.trim());
 92 | }
 93 | 
 94 | /**
 95 |  * Calculates the relevance of a sentence to a query
 96 |  * @param sentence The sentence
 97 |  * @param query The query
 98 |  * @returns A relevance score
 99 |  */
100 | function calculateSentenceRelevance(sentence: string, query: string): number {
101 |   const normalizedSentence = sentence.toLowerCase();
102 |   const normalizedQuery = query.toLowerCase();
103 |   
104 |   // Split query into words
105 |   const queryWords = normalizedQuery.split(/\s+/).filter(word => !isStopWord(word) && word.length > 2);
106 |   
107 |   // Count how many query words appear in the sentence
108 |   let matchCount = 0;
109 |   for (const word of queryWords) {
110 |     if (normalizedSentence.includes(word)) {
111 |       matchCount++;
112 |     }
113 |   }
114 |   
115 |   // Calculate score based on match percentage and sentence length
116 |   const matchPercentage = queryWords.length > 0 ? matchCount / queryWords.length : 0;
117 |   const lengthFactor = 1 - Math.min(Math.abs(normalizedSentence.length - 100) / 100, 0.5);
118 |   
119 |   return matchPercentage * 0.7 + lengthFactor * 0.3;
120 | }
121 | 
122 | /**
123 |  * Checks if a word is a common stop word
124 |  * @param word The word to check
125 |  * @returns True if it's a stop word
126 |  */
127 | function isStopWord(word: string): boolean {
128 |   const stopWords = [
129 |     'a', 'an', 'the', 'and', 'or', 'but', 'if', 'because', 'as', 'what',
130 |     'which', 'this', 'that', 'these', 'those', 'then', 'just', 'so', 'than',
131 |     'such', 'both', 'through', 'about', 'for', 'is', 'of', 'while', 'during',
132 |     'to', 'from', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further',
133 |     'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any',
134 |     'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor',
135 |     'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can',
136 |     'will', 'just', 'don', 'should', 'now'
137 |   ];
138 |   
139 |   return stopWords.includes(word.toLowerCase());
140 | } 
```

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

```typescript
  1 | /**
  2 |  * Utilities for synthesizing research results into a coherent report
  3 |  */
  4 | 
  5 | import { SubQuestion } from '../types.js';
  6 | 
  7 | /**
  8 |  * Synthesizes research results into a coherent report
  9 |  * @param mainQuestion The main research question
 10 |  * @param subQuestions Array of sub-questions with their analysis
 11 |  * @returns Synthesized research report
 12 |  */
 13 | export async function synthesizeReport(
 14 |   mainQuestion: string,
 15 |   subQuestions: SubQuestion[]
 16 | ): Promise<string> {
 17 |   if (!subQuestions || subQuestions.length === 0) {
 18 |     return `No research data available for question: "${mainQuestion}"`;
 19 |   }
 20 | 
 21 |   // Filter completed sub-questions
 22 |   const completedSubQuestions = subQuestions.filter(sq => sq.status === 'completed' && sq.analysis);
 23 |   
 24 |   if (completedSubQuestions.length === 0) {
 25 |     return `Research is still in progress for question: "${mainQuestion}"`;
 26 |   }
 27 |   
 28 |   // Generate introduction
 29 |   const introduction = generateIntroduction(mainQuestion);
 30 |   
 31 |   // Generate sections for each sub-question
 32 |   const sections = completedSubQuestions.map(sq => {
 33 |     return generateSection(sq.question, sq.analysis || '', sq.searchResults || []);
 34 |   });
 35 |   
 36 |   // Generate conclusion
 37 |   const conclusion = generateConclusion(mainQuestion, completedSubQuestions);
 38 |   
 39 |   // Generate sources section
 40 |   const sources = generateSources(completedSubQuestions);
 41 |   
 42 |   // Combine all parts into a complete report
 43 |   const report = `
 44 | # Research Report: ${mainQuestion}
 45 | 
 46 | ## Introduction
 47 | ${introduction}
 48 | 
 49 | ${sections.join('\n\n')}
 50 | 
 51 | ## Conclusion
 52 | ${conclusion}
 53 | 
 54 | ## Sources
 55 | ${sources}
 56 | `;
 57 | 
 58 |   return report;
 59 | }
 60 | 
 61 | /**
 62 |  * Generates the introduction section
 63 |  * @param mainQuestion The main research question
 64 |  * @returns Introduction text
 65 |  */
 66 | function generateIntroduction(mainQuestion: string): string {
 67 |   return `This report presents a comprehensive analysis of the question: "${mainQuestion}". 
 68 | The research was conducted using multiple sources and approaches to provide a thorough understanding of the topic.`;
 69 | }
 70 | 
 71 | /**
 72 |  * Generates a section for a sub-question
 73 |  * @param question The sub-question
 74 |  * @param analysis The analysis text
 75 |  * @param searchResults The search results
 76 |  * @returns Formatted section text
 77 |  */
 78 | function generateSection(
 79 |   question: string,
 80 |   analysis: string,
 81 |   searchResults: { title: string; url: string }[]
 82 | ): string {
 83 |   // Extract a section title from the question
 84 |   const sectionTitle = question.replace(/\?/g, '').trim();
 85 |   
 86 |   // Format the section
 87 |   return `## ${sectionTitle}
 88 | 
 89 | ${analysis}
 90 | 
 91 | ${generateCitations(analysis, searchResults)}`;
 92 | }
 93 | 
 94 | /**
 95 |  * Generates citation markers for the analysis text
 96 |  * @param analysis The analysis text
 97 |  * @param searchResults The search results
 98 |  * @returns Text with citation markers
 99 |  */
100 | function generateCitations(
101 |   analysis: string,
102 |   searchResults: { title: string; url: string }[]
103 | ): string {
104 |   // In a real implementation, this would use NLP to match text to sources
105 |   // For the prototype, we'll use a simple approach
106 |   
107 |   if (!searchResults || searchResults.length === 0) {
108 |     return '';
109 |   }
110 |   
111 |   // Create citation notes
112 |   const citations = searchResults.map((result, index) => {
113 |     return `[${index + 1}] ${result.title} - ${result.url}`;
114 |   });
115 |   
116 |   return `**Sources:**\n${citations.join('\n')}`;
117 | }
118 | 
119 | /**
120 |  * Generates the conclusion section
121 |  * @param mainQuestion The main research question
122 |  * @param subQuestions The sub-questions with analysis
123 |  * @returns Conclusion text
124 |  */
125 | function generateConclusion(
126 |   mainQuestion: string,
127 |   subQuestions: SubQuestion[]
128 | ): string {
129 |   return `This research has explored various aspects of ${extractMainTopic(mainQuestion)}. 
130 | The findings from different sub-questions provide a comprehensive understanding of the topic.
131 | The research was based on ${subQuestions.length} sub-questions and utilized multiple sources to ensure accuracy and depth.`;
132 | }
133 | 
134 | /**
135 |  * Generates the sources section
136 |  * @param subQuestions The sub-questions with search results
137 |  * @returns Formatted sources text
138 |  */
139 | function generateSources(subQuestions: SubQuestion[]): string {
140 |   // Collect all unique sources
141 |   const allSources = new Map<string, { title: string; url: string }>();
142 |   
143 |   subQuestions.forEach(sq => {
144 |     if (sq.searchResults) {
145 |       sq.searchResults.forEach(result => {
146 |         if (!allSources.has(result.url)) {
147 |           allSources.set(result.url, { title: result.title, url: result.url });
148 |         }
149 |       });
150 |     }
151 |   });
152 |   
153 |   // Format the sources
154 |   const sourcesList = Array.from(allSources.values()).map((source, index) => {
155 |     return `${index + 1}. ${source.title} - ${source.url}`;
156 |   });
157 |   
158 |   return sourcesList.join('\n');
159 | }
160 | 
161 | /**
162 |  * Extracts the main topic from a question
163 |  * @param question The question
164 |  * @returns The main topic
165 |  */
166 | function extractMainTopic(question: string): string {
167 |   // Remove question words at the beginning
168 |   const withoutQuestionWords = question
169 |     .replace(/^(what|who|when|where|why|how|is|are|do|does|did|can|could|would|should|will)\s+/i, '')
170 |     .trim();
171 |   
172 |   // If the question starts with "the", "a", "an", remove the article
173 |   const withoutArticles = withoutQuestionWords
174 |     .replace(/^(the|a|an)\s+/i, '')
175 |     .trim();
176 |   
177 |   // If there's anything left, return it as the main topic
178 |   return withoutArticles.length > 0 ? withoutArticles : question;
179 | } 
```

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

```typescript
  1 | /**
  2 |  * MCP tool for Brave Search
  3 |  */
  4 | 
  5 | import { MCPTool, MCPToolDefinition, MCPToolResponse } from '../mcp.js';
  6 | import { performSearch } from '../utils/search.js';
  7 | 
  8 | /**
  9 |  * Brave Web Search tool for MCP
 10 |  */
 11 | export class BraveWebSearchTool implements MCPTool {
 12 |   private apiKey: string;
 13 | 
 14 |   constructor(apiKey: string) {
 15 |     this.apiKey = apiKey;
 16 |   }
 17 | 
 18 |   /**
 19 |    * Get the tool definition
 20 |    */
 21 |   getDefinition(): MCPToolDefinition {
 22 |     return {
 23 |       name: 'brave_web_search',
 24 |       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. ',
 25 |       parameters: {
 26 |         type: 'object',
 27 |         properties: {
 28 |           query: {
 29 |             type: 'string',
 30 |             description: 'Search query (max 400 chars, 50 words)'
 31 |           },
 32 |           count: {
 33 |             type: 'number',
 34 |             description: 'Number of results (1-20, default 10)',
 35 |             default: 10
 36 |           },
 37 |           offset: {
 38 |             type: 'number',
 39 |             description: 'Pagination offset (max 9, default 0)',
 40 |             default: 0
 41 |           }
 42 |         },
 43 |         required: ['query']
 44 |       }
 45 |     };
 46 |   }
 47 | 
 48 |   /**
 49 |    * Execute the tool
 50 |    * @param params Tool parameters
 51 |    * @returns Tool response
 52 |    */
 53 |   async execute(params: any): Promise<MCPToolResponse> {
 54 |     try {
 55 |       // Validate parameters
 56 |       if (!params.query) {
 57 |         return {
 58 |           status: 'error',
 59 |           error: 'Search query is required'
 60 |         };
 61 |       }
 62 | 
 63 |       // Limit query length
 64 |       const query = params.query.slice(0, 400);
 65 |       
 66 |       // Set count with default and limits
 67 |       const count = Math.min(Math.max(params.count || 10, 1), 20);
 68 |       
 69 |       // Set offset with default and limits
 70 |       const offset = Math.min(Math.max(params.offset || 0, 0), 9);
 71 | 
 72 |       // Perform the search
 73 |       const results = await performSearch(query, this.apiKey, count);
 74 | 
 75 |       // Format the response
 76 |       return {
 77 |         status: 'success',
 78 |         result: {
 79 |           query,
 80 |           count: results.length,
 81 |           offset,
 82 |           results: results.map(result => ({
 83 |             title: result.title,
 84 |             description: result.description,
 85 |             url: result.url,
 86 |             relevance: result.relevance
 87 |           }))
 88 |         }
 89 |       };
 90 |     } catch (error) {
 91 |       console.error('Error in Brave Web Search tool:', error);
 92 |       return {
 93 |         status: 'error',
 94 |         error: error instanceof Error ? error.message : String(error)
 95 |       };
 96 |     }
 97 |   }
 98 | }
 99 | 
100 | /**
101 |  * Brave Local Search tool for MCP
102 |  */
103 | export class BraveLocalSearchTool implements MCPTool {
104 |   private apiKey: string;
105 | 
106 |   constructor(apiKey: string) {
107 |     this.apiKey = apiKey;
108 |   }
109 | 
110 |   /**
111 |    * Get the tool definition
112 |    */
113 |   getDefinition(): MCPToolDefinition {
114 |     return {
115 |       name: 'brave_local_search',
116 |       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.',
117 |       parameters: {
118 |         type: 'object',
119 |         properties: {
120 |           query: {
121 |             type: 'string',
122 |             description: 'Local search query (e.g. \'pizza near Central Park\')'
123 |           },
124 |           count: {
125 |             type: 'number',
126 |             description: 'Number of results (1-20, default 5)',
127 |             default: 5
128 |           }
129 |         },
130 |         required: ['query']
131 |       }
132 |     };
133 |   }
134 | 
135 |   /**
136 |    * Execute the tool
137 |    * @param params Tool parameters
138 |    * @returns Tool response
139 |    */
140 |   async execute(params: any): Promise<MCPToolResponse> {
141 |     try {
142 |       // Validate parameters
143 |       if (!params.query) {
144 |         return {
145 |           status: 'error',
146 |           error: 'Search query is required'
147 |         };
148 |       }
149 | 
150 |       // Limit query length
151 |       const query = params.query.slice(0, 400);
152 |       
153 |       // Set count with default and limits
154 |       const count = Math.min(Math.max(params.count || 5, 1), 20);
155 | 
156 |       // For now, we'll use the web search API since we don't have direct access to local search
157 |       // In a real implementation, this would use a different endpoint
158 |       const results = await performSearch(`${query} near me`, this.apiKey, count);
159 | 
160 |       // Format the response
161 |       return {
162 |         status: 'success',
163 |         result: {
164 |           query,
165 |           count: results.length,
166 |           results: results.map(result => ({
167 |             title: result.title,
168 |             description: result.description,
169 |             url: result.url,
170 |             relevance: result.relevance
171 |           }))
172 |         }
173 |       };
174 |     } catch (error) {
175 |       console.error('Error in Brave Local Search tool:', error);
176 |       return {
177 |         status: 'error',
178 |         error: error instanceof Error ? error.message : String(error)
179 |       };
180 |     }
181 |   }
182 | } 
```

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

```typescript
  1 | /**
  2 |  * MCP tool for Sequential Thinking
  3 |  */
  4 | 
  5 | import { MCPTool, MCPToolDefinition, MCPToolResponse } from '../mcp.js';
  6 | import { v4 as uuidv4 } from 'uuid';
  7 | 
  8 | /**
  9 |  * Sequential Thinking tool for MCP
 10 |  */
 11 | export class SequentialThinkingTool implements MCPTool {
 12 |   private thoughts: Map<string, any[]> = new Map();
 13 | 
 14 |   /**
 15 |    * Get the tool definition
 16 |    */
 17 |   getDefinition(): MCPToolDefinition {
 18 |     return {
 19 |       name: 'sequentialthinking',
 20 |       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',
 21 |       parameters: {
 22 |         type: 'object',
 23 |         properties: {
 24 |           thought: {
 25 |             type: 'string',
 26 |             description: 'Your current thinking step'
 27 |           },
 28 |           nextThoughtNeeded: {
 29 |             type: 'boolean',
 30 |             description: 'Whether another thought step is needed'
 31 |           },
 32 |           thoughtNumber: {
 33 |             type: 'integer',
 34 |             description: 'Current thought number',
 35 |             minimum: 1
 36 |           },
 37 |           totalThoughts: {
 38 |             type: 'integer',
 39 |             description: 'Estimated total thoughts needed',
 40 |             minimum: 1
 41 |           },
 42 |           isRevision: {
 43 |             type: 'boolean',
 44 |             description: 'Whether this revises previous thinking'
 45 |           },
 46 |           revisesThought: {
 47 |             type: 'integer',
 48 |             description: 'Which thought is being reconsidered',
 49 |             minimum: 1
 50 |           },
 51 |           branchFromThought: {
 52 |             type: 'integer',
 53 |             description: 'Branching point thought number',
 54 |             minimum: 1
 55 |           },
 56 |           branchId: {
 57 |             type: 'string',
 58 |             description: 'Branch identifier'
 59 |           },
 60 |           needsMoreThoughts: {
 61 |             type: 'boolean',
 62 |             description: 'If more thoughts are needed'
 63 |           }
 64 |         },
 65 |         required: ['thought', 'nextThoughtNeeded', 'thoughtNumber', 'totalThoughts']
 66 |       }
 67 |     };
 68 |   }
 69 | 
 70 |   /**
 71 |    * Execute the tool
 72 |    * @param params Tool parameters
 73 |    * @returns Tool response
 74 |    */
 75 |   async execute(params: any): Promise<MCPToolResponse> {
 76 |     try {
 77 |       // Validate parameters
 78 |       if (!params.thought) {
 79 |         return {
 80 |           status: 'error',
 81 |           error: 'Thought content is required'
 82 |         };
 83 |       }
 84 | 
 85 |       if (params.thoughtNumber === undefined || params.thoughtNumber < 1) {
 86 |         return {
 87 |           status: 'error',
 88 |           error: 'Valid thought number is required'
 89 |         };
 90 |       }
 91 | 
 92 |       if (params.totalThoughts === undefined || params.totalThoughts < 1) {
 93 |         return {
 94 |           status: 'error',
 95 |           error: 'Valid total thoughts is required'
 96 |         };
 97 |       }
 98 | 
 99 |       // Generate a session ID if this is the first thought
100 |       let sessionId = '';
101 |       if (params.thoughtNumber === 1) {
102 |         sessionId = uuidv4();
103 |         this.thoughts.set(sessionId, []);
104 |       } else {
105 |         // Find the session ID from previous thoughts
106 |         for (const [id, thoughts] of this.thoughts.entries()) {
107 |           if (thoughts.length > 0 && thoughts.some(t => t.thoughtNumber === params.thoughtNumber - 1)) {
108 |             sessionId = id;
109 |             break;
110 |           }
111 |         }
112 | 
113 |         if (!sessionId) {
114 |           // If we can't find a previous session, create a new one
115 |           sessionId = uuidv4();
116 |           this.thoughts.set(sessionId, []);
117 |         }
118 |       }
119 | 
120 |       // Store the thought
121 |       const thought = {
122 |         thoughtNumber: params.thoughtNumber,
123 |         totalThoughts: params.totalThoughts,
124 |         content: params.thought,
125 |         nextThoughtNeeded: params.nextThoughtNeeded,
126 |         isRevision: params.isRevision || false,
127 |         revisesThought: params.revisesThought,
128 |         branchFromThought: params.branchFromThought,
129 |         branchId: params.branchId,
130 |         needsMoreThoughts: params.needsMoreThoughts || false,
131 |         timestamp: Date.now()
132 |       };
133 | 
134 |       const thoughts = this.thoughts.get(sessionId) || [];
135 |       thoughts.push(thought);
136 |       this.thoughts.set(sessionId, thoughts);
137 | 
138 |       // Format the response
139 |       return {
140 |         status: 'success',
141 |         result: {
142 |           sessionId,
143 |           thoughtNumber: params.thoughtNumber,
144 |           totalThoughts: params.totalThoughts,
145 |           nextThoughtNeeded: params.nextThoughtNeeded,
146 |           previousThoughts: thoughts
147 |             .filter(t => t.thoughtNumber < params.thoughtNumber)
148 |             .map(t => ({
149 |               thoughtNumber: t.thoughtNumber,
150 |               content: t.content,
151 |               isRevision: t.isRevision,
152 |               revisesThought: t.revisesThought
153 |             }))
154 |         }
155 |       };
156 |     } catch (error) {
157 |       console.error('Error in Sequential Thinking tool:', error);
158 |       return {
159 |         status: 'error',
160 |         error: error instanceof Error ? error.message : String(error)
161 |       };
162 |     }
163 |   }
164 | } 
```

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

```typescript
  1 | /**
  2 |  * MCP tool for Deep Research
  3 |  * Combines Sequential Thinking and Brave Search for comprehensive research
  4 |  */
  5 | 
  6 | import { MCPTool, MCPToolDefinition, MCPToolResponse } from '../mcp.js';
  7 | import { v4 as uuidv4 } from 'uuid';
  8 | import { analyzeQuestion } from '../utils/question.js';
  9 | import { performSearch } from '../utils/search.js';
 10 | import { analyzeResults } from '../utils/analysis.js';
 11 | import { synthesizeReport } from '../utils/synthesis.js';
 12 | import { ResearchData, ResearchStatus, ResearchStep, ResearchStepType, SubQuestion } from '../types.js';
 13 | 
 14 | /**
 15 |  * Deep Research tool for MCP
 16 |  */
 17 | export class DeepResearchTool implements MCPTool {
 18 |   private apiKey: string;
 19 |   private researches: Map<string, ResearchData> = new Map();
 20 |   
 21 |   constructor(apiKey: string) {
 22 |     this.apiKey = apiKey;
 23 |   }
 24 | 
 25 |   /**
 26 |    * Get the tool definition
 27 |    */
 28 |   getDefinition(): MCPToolDefinition {
 29 |     return {
 30 |       name: 'deep_research',
 31 |       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.',
 32 |       parameters: {
 33 |         type: 'object',
 34 |         properties: {
 35 |           query: {
 36 |             type: 'string',
 37 |             description: 'The research question or topic'
 38 |           },
 39 |           maxSubQuestions: {
 40 |             type: 'number',
 41 |             description: 'Maximum number of sub-questions to generate (default: 5)',
 42 |             default: 5
 43 |           },
 44 |           maxSearchesPerQuestion: {
 45 |             type: 'number',
 46 |             description: 'Maximum number of searches per sub-question (default: 2)',
 47 |             default: 2
 48 |           },
 49 |           researchId: {
 50 |             type: 'string',
 51 |             description: 'ID of an existing research to continue or retrieve'
 52 |           },
 53 |           action: {
 54 |             type: 'string',
 55 |             description: 'Action to perform: "start", "status", "continue", or "report"',
 56 |             default: 'start'
 57 |           }
 58 |         },
 59 |         required: ['query']
 60 |       }
 61 |     };
 62 |   }
 63 | 
 64 |   /**
 65 |    * Execute the tool
 66 |    * @param params Tool parameters
 67 |    * @returns Tool response
 68 |    */
 69 |   async execute(params: any): Promise<MCPToolResponse> {
 70 |     try {
 71 |       // Validate parameters
 72 |       if (!params.query && !params.researchId) {
 73 |         return {
 74 |           status: 'error',
 75 |           error: 'Either query or researchId is required'
 76 |         };
 77 |       }
 78 | 
 79 |       // Determine the action
 80 |       const action = params.action || 'start';
 81 |       
 82 |       // Handle different actions
 83 |       switch (action) {
 84 |         case 'start':
 85 |           return this.startResearch(params);
 86 |         case 'status':
 87 |           return this.getResearchStatus(params);
 88 |         case 'continue':
 89 |           return this.continueResearch(params);
 90 |         case 'report':
 91 |           return this.generateReport(params);
 92 |         default:
 93 |           return {
 94 |             status: 'error',
 95 |             error: `Invalid action: ${action}`
 96 |           };
 97 |       }
 98 |     } catch (error) {
 99 |       console.error('Error in Deep Research tool:', error);
100 |       return {
101 |         status: 'error',
102 |         error: error instanceof Error ? error.message : String(error)
103 |       };
104 |     }
105 |   }
106 | 
107 |   /**
108 |    * Start a new research
109 |    * @param params Tool parameters
110 |    * @returns Tool response
111 |    */
112 |   private async startResearch(params: any): Promise<MCPToolResponse> {
113 |     // Generate a research ID
114 |     const researchId = uuidv4();
115 |     
116 |     // Create a new research data object
117 |     const research: ResearchData = {
118 |       id: researchId,
119 |       question: params.query,
120 |       subQuestions: [],
121 |       steps: [],
122 |       status: ResearchStatus.PLANNING,
123 |       startTime: Date.now()
124 |     };
125 |     
126 |     // Store the research
127 |     this.researches.set(researchId, research);
128 |     
129 |     // Add the first step - question analysis
130 |     const step: ResearchStep = {
131 |       id: uuidv4(),
132 |       type: ResearchStepType.QUESTION_ANALYSIS,
133 |       content: `Analyzing question: "${params.query}"`,
134 |       timestamp: Date.now()
135 |     };
136 |     
137 |     research.steps.push(step);
138 |     
139 |     // Analyze the question to generate sub-questions
140 |     const subQuestions = await analyzeQuestion(params.query);
141 |     
142 |     // Limit the number of sub-questions
143 |     const maxSubQuestions = Math.min(params.maxSubQuestions || 5, 10);
144 |     const limitedSubQuestions = subQuestions.slice(0, maxSubQuestions);
145 |     
146 |     // Create sub-question objects
147 |     research.subQuestions = limitedSubQuestions.map(question => ({
148 |       id: uuidv4(),
149 |       question,
150 |       status: 'pending'
151 |     }));
152 |     
153 |     // Update the step with the sub-questions
154 |     step.content = `Analyzed question: "${params.query}"\nGenerated ${research.subQuestions.length} sub-questions:\n${research.subQuestions.map(sq => `- ${sq.question}`).join('\n')}`;
155 |     
156 |     // Update the research status
157 |     research.status = ResearchStatus.SEARCHING;
158 |     
159 |     // Return the response
160 |     return {
161 |       status: 'success',
162 |       result: {
163 |         researchId,
164 |         question: params.query,
165 |         subQuestions: research.subQuestions.map(sq => sq.question),
166 |         status: research.status
167 |       }
168 |     };
169 |   }
170 | 
171 |   /**
172 |    * Get the status of a research
173 |    * @param params Tool parameters
174 |    * @returns Tool response
175 |    */
176 |   private getResearchStatus(params: any): Promise<MCPToolResponse> {
177 |     // Get the research ID
178 |     const researchId = params.researchId;
179 |     
180 |     // Check if the research exists
181 |     if (!researchId || !this.researches.has(researchId)) {
182 |       return Promise.resolve({
183 |         status: 'error',
184 |         error: `Research not found: ${researchId}`
185 |       });
186 |     }
187 |     
188 |     // Get the research
189 |     const research = this.researches.get(researchId)!;
190 |     
191 |     // Return the status
192 |     return Promise.resolve({
193 |       status: 'success',
194 |       result: {
195 |         researchId,
196 |         question: research.question,
197 |         status: research.status,
198 |         subQuestions: research.subQuestions.map(sq => ({
199 |           question: sq.question,
200 |           status: sq.status
201 |         })),
202 |         steps: research.steps.length,
203 |         startTime: research.startTime,
204 |         endTime: research.endTime
205 |       }
206 |     });
207 |   }
208 | 
209 |   /**
210 |    * Continue a research
211 |    * @param params Tool parameters
212 |    * @returns Tool response
213 |    */
214 |   private async continueResearch(params: any): Promise<MCPToolResponse> {
215 |     // Get the research ID
216 |     const researchId = params.researchId;
217 |     
218 |     // Check if the research exists
219 |     if (!researchId || !this.researches.has(researchId)) {
220 |       return {
221 |         status: 'error',
222 |         error: `Research not found: ${researchId}`
223 |       };
224 |     }
225 |     
226 |     // Get the research
227 |     const research = this.researches.get(researchId)!;
228 |     
229 |     // Check if the research is already completed
230 |     if (research.status === ResearchStatus.COMPLETED) {
231 |       return {
232 |         status: 'success',
233 |         result: {
234 |           researchId,
235 |           question: research.question,
236 |           status: research.status,
237 |           message: 'Research is already completed'
238 |         }
239 |       };
240 |     }
241 |     
242 |     // Find the next pending sub-question
243 |     const nextSubQuestion = research.subQuestions.find(sq => sq.status === 'pending');
244 |     
245 |     // If there are no more pending sub-questions, synthesize the results
246 |     if (!nextSubQuestion) {
247 |       return this.synthesizeResults(research);
248 |     }
249 |     
250 |     // Mark the sub-question as in-progress
251 |     nextSubQuestion.status = 'in-progress';
252 |     
253 |     // Add a search step
254 |     const searchStep: ResearchStep = {
255 |       id: uuidv4(),
256 |       type: ResearchStepType.SEARCH,
257 |       content: `Searching for: "${nextSubQuestion.question}"`,
258 |       timestamp: Date.now()
259 |     };
260 |     
261 |     research.steps.push(searchStep);
262 |     
263 |     // Perform the search
264 |     const searchResults = await performSearch(nextSubQuestion.question, this.apiKey, 10);
265 |     
266 |     // Store the search results
267 |     nextSubQuestion.searchResults = searchResults;
268 |     
269 |     // Update the search step
270 |     searchStep.content = `Searched for: "${nextSubQuestion.question}"\nFound ${searchResults.length} results`;
271 |     
272 |     // Add an analysis step
273 |     const analysisStep: ResearchStep = {
274 |       id: uuidv4(),
275 |       type: ResearchStepType.RESULT_ANALYSIS,
276 |       content: `Analyzing results for: "${nextSubQuestion.question}"`,
277 |       timestamp: Date.now()
278 |     };
279 |     
280 |     research.steps.push(analysisStep);
281 |     
282 |     // Analyze the results
283 |     const analysis = await analyzeResults(searchResults, nextSubQuestion.question);
284 |     
285 |     // Store the analysis
286 |     nextSubQuestion.analysis = analysis;
287 |     
288 |     // Update the analysis step
289 |     analysisStep.content = `Analyzed results for: "${nextSubQuestion.question}"`;
290 |     
291 |     // Mark the sub-question as completed
292 |     nextSubQuestion.status = 'completed';
293 |     
294 |     // Return the response
295 |     return {
296 |       status: 'success',
297 |       result: {
298 |         researchId,
299 |         question: research.question,
300 |         subQuestionCompleted: nextSubQuestion.question,
301 |         remainingSubQuestions: research.subQuestions.filter(sq => sq.status === 'pending').length,
302 |         status: research.status
303 |       }
304 |     };
305 |   }
306 | 
307 |   /**
308 |    * Synthesize the results of a research
309 |    * @param research The research data
310 |    * @returns Tool response
311 |    */
312 |   private async synthesizeResults(research: ResearchData): Promise<MCPToolResponse> {
313 |     // Add a synthesis step
314 |     const synthesisStep: ResearchStep = {
315 |       id: uuidv4(),
316 |       type: ResearchStepType.SYNTHESIS,
317 |       content: 'Synthesizing research results',
318 |       timestamp: Date.now()
319 |     };
320 |     
321 |     research.steps.push(synthesisStep);
322 |     
323 |     // Update the research status
324 |     research.status = ResearchStatus.SYNTHESIZING;
325 |     
326 |     // Synthesize the report
327 |     const report = await synthesizeReport(research.question, research.subQuestions);
328 |     
329 |     // Store the report
330 |     research.report = report;
331 |     
332 |     // Update the synthesis step
333 |     synthesisStep.content = 'Synthesized research results';
334 |     
335 |     // Update the research status and end time
336 |     research.status = ResearchStatus.COMPLETED;
337 |     research.endTime = Date.now();
338 |     
339 |     // Return the response
340 |     return {
341 |       status: 'success',
342 |       result: {
343 |         researchId: research.id,
344 |         question: research.question,
345 |         status: research.status,
346 |         message: 'Research completed',
347 |         reportPreview: report.substring(0, 500) + '...'
348 |       }
349 |     };
350 |   }
351 | 
352 |   /**
353 |    * Generate a report for a research
354 |    * @param params Tool parameters
355 |    * @returns Tool response
356 |    */
357 |   private generateReport(params: any): Promise<MCPToolResponse> {
358 |     // Get the research ID
359 |     const researchId = params.researchId;
360 |     
361 |     // Check if the research exists
362 |     if (!researchId || !this.researches.has(researchId)) {
363 |       return Promise.resolve({
364 |         status: 'error',
365 |         error: `Research not found: ${researchId}`
366 |       });
367 |     }
368 |     
369 |     // Get the research
370 |     const research = this.researches.get(researchId)!;
371 |     
372 |     // Check if the research is completed
373 |     if (research.status !== ResearchStatus.COMPLETED) {
374 |       return Promise.resolve({
375 |         status: 'error',
376 |         error: 'Research is not yet completed'
377 |       });
378 |     }
379 |     
380 |     // Return the report
381 |     return Promise.resolve({
382 |       status: 'success',
383 |       result: {
384 |         researchId,
385 |         question: research.question,
386 |         report: research.report,
387 |         subQuestions: research.subQuestions.length,
388 |         steps: research.steps.length,
389 |         startTime: research.startTime,
390 |         endTime: research.endTime,
391 |         duration: (research.endTime! - research.startTime) / 1000
392 |       }
393 |     });
394 |   }
395 | } 
```