@oevortex/ddg_search 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # Changelog
2
-
2
+
3
3
  All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.3.0] - 2026-02-09
6
+
7
+ ### Removed
8
+ - Brave AI Search provider and tool
9
+ - Brave AI search utility with streaming response parsing
10
+ - Brave AI tool tests and MCP integration coverage
11
+
12
+ ### Changed
13
+ - Updated package.json version to 1.3.0
14
+
4
15
  ## [1.2.1] - 2026-01-19
5
16
  ### Added
6
17
  - Brave AI Search provider and tool with research mode toggle
package/README.md CHANGED
@@ -211,15 +211,6 @@ Or if installed globally:
211
211
  </ul>
212
212
  <i>Example: Search Monica AI for "Latest advancements in AI"</i>
213
213
  </div>
214
- <div style="margin-bottom: 1.5em;">
215
- <b>🛡️ Brave AI Search Tool</b><br/>
216
- <code>brave-search</code><br/>
217
- <ul>
218
- <li><b>query</b> (string, required): The search query or question</li>
219
- <li><b>enableResearch</b> (boolean, optional, default: false): Enable deep research mode</li>
220
- </ul>
221
- <i>Example: Search Brave AI for "Summarize the latest AI safety research"</i>
222
- </div>
223
214
  </div>
224
215
 
225
216
  ---
@@ -235,13 +226,11 @@ src/
235
226
  searchTool.js
236
227
  iaskTool.js
237
228
  monicaTool.js
238
- braveTool.js
239
229
  utils/
240
230
  search.js # Search and URL utilities
241
231
  user_agents.js
242
232
  search_monica.js
243
233
  search_iask.js # IAsk AI search utilities
244
- search_brave_ai.js
245
234
  package.json
246
235
  README.md
247
236
  ```
package/babel.config.js CHANGED
@@ -1,4 +1,4 @@
1
- module.exports = {
1
+ export default {
2
2
  presets: [
3
3
  [
4
4
  '@babel/preset-env',
package/bin/cli.js CHANGED
@@ -14,13 +14,12 @@ async function startServer() {
14
14
  const { searchToolDefinition, searchToolHandler } = await import(`${modulePath}/tools/searchTool.js`);
15
15
  const { iaskToolDefinition, iaskToolHandler } = await import(`${modulePath}/tools/iaskTool.js`);
16
16
  const { monicaToolDefinition, monicaToolHandler } = await import(`${modulePath}/tools/monicaTool.js`);
17
- const { braveToolDefinition, braveToolHandler } = await import(`${modulePath}/tools/braveTool.js`);
18
17
 
19
18
  // Create the MCP server
20
19
  const server = new Server({
21
20
  id: 'ddg-search-mcp',
22
- name: 'DuckDuckGo, IAsk AI, Monica & Brave AI Search MCP',
23
- description: 'A Model Context Protocol server for web search using DuckDuckGo, IAsk AI, Monica, and Brave AI',
21
+ name: 'DuckDuckGo, IAsk AI & Monica AI Search MCP',
22
+ description: 'A Model Context Protocol server for web search using DuckDuckGo, IAsk AI, and Monica AI',
24
23
  version: '1.1.8'
25
24
  }, {
26
25
  capabilities: {
@@ -34,8 +33,7 @@ async function startServer() {
34
33
  let availableTools = [
35
34
  searchToolDefinition,
36
35
  iaskToolDefinition,
37
- monicaToolDefinition,
38
- braveToolDefinition
36
+ monicaToolDefinition
39
37
  ];
40
38
 
41
39
  // Define available tools
@@ -58,7 +56,7 @@ async function startServer() {
58
56
  const { name, arguments: args } = request.params;
59
57
 
60
58
  // Validate tool name
61
- const validTools = ['web-search', 'iask-search', 'monica-search', 'brave-search'];
59
+ const validTools = ['web-search', 'iask-search', 'monica-search'];
62
60
  if (!validTools.includes(name)) {
63
61
  throw new Error(`Unknown tool: ${name}`);
64
62
  }
@@ -74,9 +72,6 @@ async function startServer() {
74
72
  case 'monica-search':
75
73
  return await monicaToolHandler(args);
76
74
 
77
- case 'brave-search':
78
- return await braveToolHandler(args);
79
-
80
75
  default:
81
76
  throw new Error(`Tool not found: ${name}`);
82
77
  }
@@ -106,7 +101,7 @@ async function startServer() {
106
101
  // Start the server with stdio transport
107
102
  const transport = new StdioServerTransport();
108
103
  await server.connect(transport);
109
- console.error('DuckDuckGo, IAsk AI, Monica & Brave AI Search MCP server started and listening on stdio');
104
+ console.error('DuckDuckGo, IAsk AI & Monica AI Search MCP server started and listening on stdio');
110
105
  } catch (error) {
111
106
  console.error('Failed to start server:', error);
112
107
  process.exit(1);
@@ -120,7 +115,7 @@ const versionFlag = args.includes('--version') || args.includes('-v');
120
115
 
121
116
  if (helpFlag) {
122
117
  console.log(`
123
- DuckDuckGo, IAsk AI, Monica & Brave AI Search MCP - A Model Context Protocol server for web search
118
+ DuckDuckGo, IAsk AI & Monica AI Search MCP - A Model Context Protocol server for web search
124
119
 
125
120
  Usage:
126
121
  npx -y @oevortex/ddg_search@latest [options]
@@ -133,7 +128,6 @@ This MCP server provides the following tools:
133
128
  - web-search: Search the web using DuckDuckGo
134
129
  - iask-search: Search using IAsk AI for AI-generated responses
135
130
  - monica-search: Search using Monica AI for AI-generated responses
136
- - brave-search: Search using Brave AI for AI-generated responses
137
131
 
138
132
  Created by @OEvortex
139
133
  Subscribe to youtube.com/@OEvortex for more tools and tutorials!
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oevortex/ddg_search",
3
- "version": "1.2.1",
4
- "description": "A Model Context Protocol server for web search using DuckDuckGo and IAsk AI",
3
+ "version": "1.3.0",
4
+ "description": "A Model Context Protocol server for web search using DuckDuckGo, IAsk AI, and Monica AI",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.ts",
7
7
  "exports": {
package/src/index.js CHANGED
@@ -5,7 +5,6 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
5
5
  import { searchToolDefinition, searchToolHandler } from './tools/searchTool.js';
6
6
  import { iaskToolDefinition, iaskToolHandler } from './tools/iaskTool.js';
7
7
  import { monicaToolDefinition, monicaToolHandler } from './tools/monicaTool.js';
8
- import { braveToolDefinition, braveToolHandler } from './tools/braveTool.js';
9
8
 
10
9
  // Required: Export default createServer function for Smithery
11
10
  export default function createServer({ config } = {}) {
@@ -15,8 +14,7 @@ export default function createServer({ config } = {}) {
15
14
  const availableTools = [
16
15
  searchToolDefinition,
17
16
  iaskToolDefinition,
18
- monicaToolDefinition,
19
- braveToolDefinition
17
+ monicaToolDefinition
20
18
  ];
21
19
 
22
20
  console.log('Available tools:', availableTools.map(t => t.name));
@@ -58,9 +56,6 @@ export default function createServer({ config } = {}) {
58
56
  case 'monica-search':
59
57
  return await monicaToolHandler(args);
60
58
 
61
- case 'brave-search':
62
- return await braveToolHandler(args);
63
-
64
59
  default:
65
60
  throw new Error(`Tool not found: ${name}`);
66
61
  }
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
5
5
  import { searchToolDefinition, searchToolHandler } from './tools/searchTool.js';
6
6
  import { iaskToolDefinition, iaskToolHandler } from './tools/iaskTool.js';
7
7
  import { monicaToolDefinition, monicaToolHandler } from './tools/monicaTool.js';
8
- import { braveToolDefinition, braveToolHandler } from './tools/braveTool.js';
9
8
 
10
9
  // Required: Export default createServer function for Smithery
11
10
  export default function createServer({ config }: { config?: any } = {}) {
@@ -15,8 +14,7 @@ export default function createServer({ config }: { config?: any } = {}) {
15
14
  const availableTools = [
16
15
  searchToolDefinition,
17
16
  iaskToolDefinition,
18
- monicaToolDefinition,
19
- braveToolDefinition
17
+ monicaToolDefinition
20
18
  ];
21
19
 
22
20
  console.log('Available tools:', availableTools.map(t => t.name));
@@ -58,9 +56,6 @@ export default function createServer({ config }: { config?: any } = {}) {
58
56
  case 'monica-search':
59
57
  return await monicaToolHandler(args);
60
58
 
61
- case 'brave-search':
62
- return await braveToolHandler(args);
63
-
64
59
  default:
65
60
  throw new Error(`Tool not found: ${name}`);
66
61
  }
package/test.setup.js CHANGED
@@ -68,53 +68,6 @@ jest.mock('axios-cookiejar-support', () => ({
68
68
  wrapper: jest.fn((axios) => axios)
69
69
  }));
70
70
 
71
- jest.mock('crypto', () => ({
72
- randomUUID: jest.fn(() => 'mock-uuid-12345')
73
- }));
74
-
75
- // Mock axios
76
- const mockAxios = jest.fn(() => Promise.resolve({
77
- status: 200,
78
- data: '<html><body>Mock Response</body></html>',
79
- config: { url: 'http://example.com' },
80
- request: { res: { responseUrl: 'http://example.com' } }
81
- }));
82
-
83
- mockAxios.get = jest.fn();
84
- mockAxios.post = jest.fn();
85
-
86
- jest.mock('axios', () => mockAxios);
87
-
88
- // Mock external modules
89
- jest.mock('cheerio', () => ({
90
- load: jest.fn(() => ({
91
- find: jest.fn(() => ({
92
- each: jest.fn(),
93
- text: jest.fn(() => 'Mock Title'),
94
- attr: jest.fn(() => 'http://example.com')
95
- })),
96
- html: jest.fn(() => '<div>Mock Content</div>'),
97
- text: jest.fn(() => 'Mock Text Content')
98
- }))
99
- }));
100
-
101
- jest.mock('ws', () => jest.fn());
102
-
103
- jest.mock('turndown', () => jest.fn(() => ({
104
- turndown: jest.fn((html) => html.replace(/<[^>]*>/g, ''))
105
- })));
106
-
107
- jest.mock('tough-cookie', () => ({
108
- CookieJar: jest.fn(() => ({
109
- getCookies: jest.fn(() => []),
110
- setCookie: jest.fn()
111
- }))
112
- }));
113
-
114
- jest.mock('axios-cookiejar-support', () => ({
115
- wrapper: jest.fn((axios) => axios)
116
- }));
117
-
118
71
  jest.mock('crypto', () => ({
119
72
  randomUUID: jest.fn(() => 'mock-uuid-12345')
120
73
  }));
@@ -1,63 +0,0 @@
1
- import { searchBraveAI } from '../utils/search_brave_ai.js';
2
-
3
- /**
4
- * Brave AI search tool definition
5
- */
6
- export const braveToolDefinition = {
7
- name: 'brave-search',
8
- title: 'Brave AI Search',
9
- description: 'AI-powered search using Brave Search AI Chat. Returns AI-generated responses based on web content.',
10
- inputSchema: {
11
- type: 'object',
12
- properties: {
13
- query: {
14
- type: 'string',
15
- description: 'The search query or question.'
16
- },
17
- enableResearch: {
18
- type: 'boolean',
19
- description: 'Enable deep research mode for more comprehensive responses.',
20
- default: false
21
- }
22
- },
23
- required: ['query']
24
- },
25
- annotations: {
26
- readOnlyHint: true,
27
- openWorldHint: false
28
- }
29
- };
30
-
31
- /**
32
- * Brave AI search tool handler
33
- * @param {Object} params - The tool parameters
34
- * @returns {Promise<Object>} - The tool result
35
- */
36
- export async function braveToolHandler(params) {
37
- const { query, enableResearch = false } = params;
38
-
39
- console.log(`Searching Brave AI for: "${query}" (research: ${enableResearch})`);
40
-
41
- try {
42
- const result = await searchBraveAI(query, { enableResearch });
43
- return {
44
- content: [
45
- {
46
- type: 'text',
47
- text: result || 'No results found.'
48
- }
49
- ]
50
- };
51
- } catch (error) {
52
- console.error(`Error in Brave AI search: ${error.message}`);
53
- return {
54
- isError: true,
55
- content: [
56
- {
57
- type: 'text',
58
- text: `Error searching Brave AI: ${error.message}`
59
- }
60
- ]
61
- };
62
- }
63
- }
@@ -1,167 +0,0 @@
1
- import axios from 'axios';
2
- import { randomBytes } from 'crypto';
3
- import { getRandomUserAgent } from './user_agents.js';
4
-
5
- const BASE_URL = 'https://search.brave.com/api/tap/v1';
6
- const DEFAULT_TIMEOUT = 30000;
7
-
8
- function generateKeyB64() {
9
- const key = randomBytes(32);
10
- const k = key.toString('base64url');
11
- const jwk = {
12
- alg: 'A256GCM',
13
- ext: true,
14
- k,
15
- key_ops: ['encrypt', 'decrypt'],
16
- kty: 'oct'
17
- };
18
- return Buffer.from(JSON.stringify(jwk)).toString('base64');
19
- }
20
-
21
- function buildHeaders() {
22
- return {
23
- accept: 'application/json',
24
- 'accept-language': 'en-US,en;q=0.9',
25
- 'user-agent': getRandomUserAgent(),
26
- 'sec-ch-ua': '"Chromium";v="127", "Not)A;Brand";v="99"',
27
- 'sec-ch-ua-mobile': '?0',
28
- 'sec-ch-ua-platform': '"Windows"',
29
- 'sec-fetch-dest': 'empty',
30
- 'sec-fetch-mode': 'cors',
31
- 'sec-fetch-site': 'same-origin',
32
- referer: 'https://search.brave.com/ask'
33
- };
34
- }
35
-
36
- function parseStream(stream) {
37
- return new Promise((resolve, reject) => {
38
- let buffer = '';
39
- let text = '';
40
-
41
- stream.on('data', (chunk) => {
42
- buffer += chunk.toString();
43
- const lines = buffer.split('\n');
44
- buffer = lines.pop() ?? '';
45
-
46
- for (const line of lines) {
47
- const trimmed = line.trim();
48
- if (!trimmed) {
49
- continue;
50
- }
51
-
52
- try {
53
- const payload = JSON.parse(trimmed);
54
- if (payload?.type === 'text_delta') {
55
- text += payload.delta ?? '';
56
- }
57
- } catch (error) {
58
- // Ignore malformed lines
59
- }
60
- }
61
- });
62
-
63
- stream.on('end', () => resolve(text));
64
- stream.on('error', (error) => reject(error));
65
- });
66
- }
67
-
68
- /**
69
- * Search using Brave AI Search.
70
- * @param {string} prompt - The search query.
71
- * @param {object} [options] - Search options.
72
- * @param {boolean} [options.enableResearch=false] - Enable deep research mode.
73
- * @param {number} [options.timeout=30000] - Request timeout in ms.
74
- * @param {string} [options.language='en'] - Language code.
75
- * @param {string} [options.country='US'] - Country code.
76
- * @param {string} [options.uiLang='en-us'] - UI language.
77
- * @param {string|null} [options.geoloc=null] - Geolocation coordinates.
78
- * @returns {Promise<string>} AI-generated response text.
79
- */
80
- export async function searchBraveAI(
81
- prompt,
82
- {
83
- enableResearch = false,
84
- timeout = DEFAULT_TIMEOUT,
85
- language = 'en',
86
- country = 'US',
87
- uiLang = 'en-us',
88
- geoloc = null
89
- } = {}
90
- ) {
91
- if (!prompt || typeof prompt !== 'string') {
92
- throw new Error('Invalid prompt: must be a non-empty string');
93
- }
94
-
95
- if (prompt.length > 5000) {
96
- throw new Error('Invalid prompt: too long (maximum 5000 characters)');
97
- }
98
-
99
- const symmetricKey = generateKeyB64();
100
- const client = axios.create({
101
- timeout,
102
- headers: buildHeaders(),
103
- validateStatus: (status) => status >= 200 && status < 500
104
- });
105
-
106
- const newParams = {
107
- language,
108
- country,
109
- ui_lang: uiLang,
110
- symmetric_key: symmetricKey,
111
- source: enableResearch ? 'home' : 'llmSuggest',
112
- query: prompt,
113
- enable_research: enableResearch ? 'true' : 'false'
114
- };
115
-
116
- if (geoloc) {
117
- newParams.geoloc = geoloc;
118
- }
119
-
120
- try {
121
- const newResponse = await client.get(`${BASE_URL}/new`, { params: newParams });
122
- if (newResponse.status !== 200) {
123
- throw new Error(`Brave AI failed to initialize chat: HTTP ${newResponse.status}`);
124
- }
125
-
126
- const chatId = newResponse.data?.id;
127
- if (!chatId) {
128
- throw new Error('Brave AI failed to initialize chat: missing conversation id');
129
- }
130
-
131
- const streamParams = {
132
- id: chatId,
133
- query: prompt,
134
- symmetric_key: symmetricKey,
135
- language,
136
- country,
137
- ui_lang: uiLang,
138
- enable_research: enableResearch ? 'true' : 'false',
139
- enable_followups: enableResearch ? 'true' : 'false'
140
- };
141
-
142
- const referer = `https://search.brave.com/ask?q=${encodeURIComponent(prompt)}&conversation=${chatId}`;
143
- const streamResponse = await client.get(`${BASE_URL}/stream`, {
144
- params: streamParams,
145
- responseType: 'stream',
146
- headers: {
147
- referer
148
- }
149
- });
150
-
151
- if (streamResponse.status !== 200) {
152
- throw new Error(`Brave AI stream failed: HTTP ${streamResponse.status}`);
153
- }
154
-
155
- return await parseStream(streamResponse.data);
156
- } catch (error) {
157
- if (error.response?.status === 429) {
158
- throw new Error('Brave AI rate limit: too many requests');
159
- }
160
-
161
- if (error.code === 'ECONNABORTED') {
162
- throw new Error('Brave AI request timeout: took too long');
163
- }
164
-
165
- throw new Error(`Brave AI search failed for "${prompt}": ${error.message}`);
166
- }
167
- }