@oevortex/ddg_search 1.1.2 → 1.1.4
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/.dockerignore +12 -0
- package/.smithery/index.cjs +133833 -0
- package/CHANGELOG.md +19 -0
- package/Dockerfile +18 -12
- package/README.md +57 -36
- package/bin/cli.js +31 -21
- package/package.json +1 -1
- package/smithery.yaml +12 -0
- package/src/index.js +80 -67
- package/src/index.ts +80 -0
- package/src/tools/iaskTool.js +101 -0
- package/src/tools/searchTool.js +5 -9
- package/src/utils/search.js +49 -49
- package/src/utils/search_iask.js +381 -0
- package/src/tools/feloTool.js +0 -85
- package/src/tools/fetchUrlTool.js +0 -118
- package/src/tools/metadataTool.js +0 -58
- package/src/utils/search_felo.js +0 -204
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.1.3] - 2025-11-30
|
|
6
|
+
### Changed
|
|
7
|
+
- Replaced Felo AI tool with IAsk AI tool for advanced AI-powered search
|
|
8
|
+
- Added `src/utils/search_iask.js` implementing IAsk API client
|
|
9
|
+
- Added `src/tools/iaskTool.js` tool definition and handler
|
|
10
|
+
- Updated `src/index.ts` to use IAsk tool instead of Felo
|
|
11
|
+
- Updated `package.json` description, keywords, and dependencies (`turndown`, `ws`)
|
|
12
|
+
- Updated `README.md` to reference IAsk AI and document new tool parameters
|
|
13
|
+
- Removed old Felo tool files (`feloTool.js`, `search_felo.js`)
|
|
14
|
+
|
|
15
|
+
## [1.1.2] - 2025-11-29
|
|
16
|
+
### Added
|
|
17
|
+
- Initial release with DuckDuckGo and Felo AI search tools
|
|
18
|
+
- MCP server implementation
|
|
19
|
+
- Caching, rotating user agents, and web scraping features
|
package/Dockerfile
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
#
|
|
2
|
-
FROM node:
|
|
3
|
-
|
|
4
|
-
# Set working directory
|
|
1
|
+
# Build stage: install dependencies
|
|
2
|
+
FROM node:22-slim AS build
|
|
5
3
|
WORKDIR /app
|
|
6
4
|
|
|
7
|
-
# Copy package
|
|
8
|
-
COPY package
|
|
5
|
+
# Copy package manifests and lockfile first for better caching
|
|
6
|
+
COPY package.json package-lock.json ./
|
|
9
7
|
|
|
10
|
-
# Install dependencies
|
|
11
|
-
RUN npm install --production
|
|
8
|
+
# Install production dependencies (use npm ci when lockfile exists)
|
|
9
|
+
RUN if [ -f package-lock.json ]; then npm ci --production; else npm install --production; fi
|
|
12
10
|
|
|
13
|
-
# Copy source
|
|
11
|
+
# Copy application source
|
|
14
12
|
COPY . .
|
|
15
13
|
|
|
16
|
-
#
|
|
14
|
+
# Final minimal runtime image
|
|
15
|
+
FROM node:22-slim AS runtime
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
|
|
18
|
+
# Copy node_modules and built app from build stage
|
|
19
|
+
COPY --from=build /app/node_modules ./node_modules
|
|
20
|
+
COPY --from=build /app .
|
|
21
|
+
|
|
22
|
+
# Expose port in case the MCP server needs it
|
|
17
23
|
EXPOSE 3000
|
|
18
24
|
|
|
19
|
-
# Default command
|
|
20
|
-
CMD ["node", "
|
|
25
|
+
# Default command: use the CLI entry which starts the MCP server
|
|
26
|
+
CMD ["node", "bin/cli.js"]
|
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<img src="https://img.shields.io/npm/v/@oevortex/ddg_search.svg" alt="npm version" />
|
|
3
3
|
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0" />
|
|
4
4
|
<img src="https://img.shields.io/badge/YouTube-%40OEvortex-red.svg" alt="YouTube Channel" />
|
|
5
|
-
<h1>DuckDuckGo &
|
|
6
|
-
<p>A blazing-fast, privacy-friendly Model Context Protocol (MCP) server for web search and AI-powered responses using DuckDuckGo and
|
|
5
|
+
<h1>DuckDuckGo & IAsk AI Search MCP 🔍🧠</h1>
|
|
6
|
+
<p>A blazing-fast, privacy-friendly Model Context Protocol (MCP) server for web search and AI-powered responses using DuckDuckGo and IAsk AI.</p>
|
|
7
7
|
<a href="https://glama.ai/mcp/servers/@OEvortex/ddg_search">
|
|
8
8
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/@OEvortex/ddg_search/badge" alt="DuckDuckGo Search MCP server" />
|
|
9
9
|
</a>
|
|
@@ -20,9 +20,7 @@
|
|
|
20
20
|
## ✨ Features
|
|
21
21
|
|
|
22
22
|
<div style="display: flex; flex-wrap: wrap; gap: 1.5em; margin-bottom: 1.5em;"> <div><b>🌐 Web search</b> using DuckDuckGo HTML</div>
|
|
23
|
-
<div><b>🧠 AI search</b> using
|
|
24
|
-
<div><b>📄 URL content extraction</b> with smart filtering</div>
|
|
25
|
-
<div><b>📊 URL metadata extraction</b> (title, description, images)</div>
|
|
23
|
+
<div><b>🧠 AI search</b> using IAsk AI</div>
|
|
26
24
|
<div><b>⚡ Performance optimized</b> with caching</div>
|
|
27
25
|
<div><b>🛡️ Security features</b> including rate limiting and rotating user agents</div>
|
|
28
26
|
<div><b>🔌 MCP-compliant</b> server implementation</div>
|
|
@@ -52,7 +50,7 @@ npx -y @oevortex/ddg_search@latest
|
|
|
52
50
|
## 🛠️ Installation Options
|
|
53
51
|
|
|
54
52
|
<details>
|
|
55
|
-
<summary><b>Global Installation</b></summary>
|
|
53
|
+
<summary><b>Global Installation (npm)</b></summary>
|
|
56
54
|
|
|
57
55
|
```bash
|
|
58
56
|
npm install -g @oevortex/ddg_search
|
|
@@ -66,6 +64,36 @@ ddg-search-mcp
|
|
|
66
64
|
|
|
67
65
|
</details>
|
|
68
66
|
|
|
67
|
+
<details>
|
|
68
|
+
<summary><b>Global Installation (Yarn)</b></summary>
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
yarn global add @oevortex/ddg_search
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Run globally:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
ddg-search-mcp
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
</details>
|
|
81
|
+
|
|
82
|
+
<details>
|
|
83
|
+
<summary><b>Global Installation (pnpm)</b></summary>
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pnpm add -g @oevortex/ddg_search
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Run globally:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ddg-search-mcp
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
</details>
|
|
96
|
+
|
|
69
97
|
<details>
|
|
70
98
|
<summary><b>Local Installation (Development)</b></summary>
|
|
71
99
|
|
|
@@ -76,6 +104,20 @@ npm install
|
|
|
76
104
|
npm start
|
|
77
105
|
```
|
|
78
106
|
|
|
107
|
+
Or with Yarn:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
yarn install
|
|
111
|
+
yarn start
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or with pnpm:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pnpm install
|
|
118
|
+
pnpm start
|
|
119
|
+
```
|
|
120
|
+
|
|
79
121
|
</details>
|
|
80
122
|
|
|
81
123
|
---
|
|
@@ -140,34 +182,15 @@ Or if installed globally:
|
|
|
140
182
|
<i>Example: Search the web for "climate change solutions"</i>
|
|
141
183
|
</div>
|
|
142
184
|
<div style="margin-bottom: 1.5em;">
|
|
143
|
-
<b>🧠
|
|
144
|
-
<code>
|
|
185
|
+
<b>🧠 IAsk AI Search Tool</b><br/>
|
|
186
|
+
<code>iask-search</code><br/>
|
|
145
187
|
<ul>
|
|
146
|
-
<li><b>query</b> (string, required): The search query or
|
|
188
|
+
<li><b>query</b> (string, required): The search query or question</li>
|
|
189
|
+
<li><b>mode</b> (string, optional, default: "question"): Search mode - "question", "academic", "forums", "wiki", or "thinking"</li>
|
|
190
|
+
<li><b>detailLevel</b> (string, optional): Response detail level - "concise", "detailed", or "comprehensive"</li>
|
|
147
191
|
<li><b>stream</b> (boolean, optional, default: false): Whether to stream the response</li>
|
|
148
192
|
</ul>
|
|
149
|
-
<i>Example: Search
|
|
150
|
-
</div>
|
|
151
|
-
<div style="margin-bottom: 1.5em;">
|
|
152
|
-
<b>📄 Fetch URL Tool</b><br/>
|
|
153
|
-
<code>fetch-url</code><br/>
|
|
154
|
-
<ul>
|
|
155
|
-
<li><b>url</b> (string, required): The URL to fetch</li>
|
|
156
|
-
<li><b>maxLength</b> (integer, optional, default: 10000): Max content length</li>
|
|
157
|
-
<li><b>extractMainContent</b> (boolean, optional, default: true): Extract main content</li>
|
|
158
|
-
<li><b>includeLinks</b> (boolean, optional, default: true): Include link text</li>
|
|
159
|
-
<li><b>includeImages</b> (boolean, optional, default: true): Include image alt text</li>
|
|
160
|
-
<li><b>excludeTags</b> (array, optional): Tags to exclude</li>
|
|
161
|
-
</ul>
|
|
162
|
-
<i>Example: Fetch the content from "https://example.com"</i>
|
|
163
|
-
</div>
|
|
164
|
-
<div style="margin-bottom: 1.5em;">
|
|
165
|
-
<b>📊 URL Metadata Tool</b><br/>
|
|
166
|
-
<code>url-metadata</code><br/>
|
|
167
|
-
<ul>
|
|
168
|
-
<li><b>url</b> (string, required): The URL to extract metadata from</li>
|
|
169
|
-
</ul>
|
|
170
|
-
<i>Example: Get metadata for "https://example.com"</i>
|
|
193
|
+
<i>Example: Search IAsk AI for "Explain quantum computing in simple terms"</i>
|
|
171
194
|
</div>
|
|
172
195
|
</div>
|
|
173
196
|
|
|
@@ -182,12 +205,10 @@ src/
|
|
|
182
205
|
index.js # Main entry point
|
|
183
206
|
tools/ # Tool definitions and handlers
|
|
184
207
|
searchTool.js
|
|
185
|
-
|
|
186
|
-
metadataTool.js
|
|
187
|
-
feloTool.js
|
|
208
|
+
iaskTool.js
|
|
188
209
|
utils/
|
|
189
210
|
search.js # Search and URL utilities
|
|
190
|
-
|
|
211
|
+
search_iask.js # IAsk AI search utilities
|
|
191
212
|
package.json
|
|
192
213
|
README.md
|
|
193
214
|
```
|
|
@@ -227,4 +248,4 @@ Apache License 2.0
|
|
|
227
248
|
|
|
228
249
|
<div align="center">
|
|
229
250
|
<sub>Made with ❤️ by <a href="https://youtube.com/@OEvortex">@OEvortex</a></sub>
|
|
230
|
-
</div>
|
|
251
|
+
</div>
|
package/bin/cli.js
CHANGED
|
@@ -12,48 +12,58 @@ async function startServer() {
|
|
|
12
12
|
try {
|
|
13
13
|
// Dynamically import the modules
|
|
14
14
|
const { searchToolDefinition, searchToolHandler } = await import(`${modulePath}/tools/searchTool.js`);
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const { feloToolDefinition, feloToolHandler } = await import(`${modulePath}/tools/feloTool.js`);
|
|
16
|
+
|
|
17
|
+
// Create the MCP server
|
|
18
18
|
const server = new Server({
|
|
19
19
|
id: 'ddg-search-mcp',
|
|
20
20
|
name: 'DuckDuckGo & Felo AI Search MCP',
|
|
21
21
|
description: 'A Model Context Protocol server for web search using DuckDuckGo and Felo AI',
|
|
22
|
-
version: '1.1.
|
|
22
|
+
version: '1.1.2'
|
|
23
23
|
}, {
|
|
24
24
|
capabilities: {
|
|
25
|
-
tools: {
|
|
25
|
+
tools: {
|
|
26
|
+
listChanged: true
|
|
27
|
+
}
|
|
26
28
|
}
|
|
27
29
|
});
|
|
28
30
|
|
|
31
|
+
// Global variable to track available tools
|
|
32
|
+
let availableTools = [
|
|
33
|
+
searchToolDefinition,
|
|
34
|
+
feloToolDefinition
|
|
35
|
+
];
|
|
36
|
+
|
|
29
37
|
// Define available tools
|
|
30
38
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
31
39
|
return {
|
|
32
|
-
tools:
|
|
33
|
-
searchToolDefinition,
|
|
34
|
-
fetchUrlToolDefinition,
|
|
35
|
-
metadataToolDefinition,
|
|
36
|
-
feloToolDefinition
|
|
37
|
-
]
|
|
40
|
+
tools: availableTools
|
|
38
41
|
};
|
|
39
42
|
});
|
|
40
43
|
|
|
44
|
+
// Function to notify clients when tools list changes
|
|
45
|
+
function notifyToolsChanged() {
|
|
46
|
+
server.notification({
|
|
47
|
+
method: 'notifications/tools/list_changed'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
// Handle tool execution
|
|
42
52
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
43
53
|
try {
|
|
44
54
|
const { name, arguments: args } = request.params;
|
|
55
|
+
|
|
56
|
+
// Validate tool name
|
|
57
|
+
const validTools = ['web-search', 'felo-search'];
|
|
58
|
+
if (!validTools.includes(name)) {
|
|
59
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
60
|
+
}
|
|
45
61
|
|
|
46
62
|
// Route to the appropriate tool handler
|
|
47
63
|
switch (name) {
|
|
48
64
|
case 'web-search':
|
|
49
65
|
return await searchToolHandler(args);
|
|
50
66
|
|
|
51
|
-
case 'fetch-url':
|
|
52
|
-
return await fetchUrlToolHandler(args);
|
|
53
|
-
|
|
54
|
-
case 'url-metadata':
|
|
55
|
-
return await metadataToolHandler(args);
|
|
56
|
-
|
|
57
67
|
case 'felo-search':
|
|
58
68
|
return await feloToolHandler(args);
|
|
59
69
|
|
|
@@ -61,13 +71,15 @@ async function startServer() {
|
|
|
61
71
|
throw new Error(`Tool not found: ${name}`);
|
|
62
72
|
}
|
|
63
73
|
} catch (error) {
|
|
64
|
-
console.error(`Error handling ${request.params.name}
|
|
74
|
+
console.error(`Error handling ${request.params.name} tool call:`, error);
|
|
75
|
+
|
|
76
|
+
// Return proper tool execution error format
|
|
65
77
|
return {
|
|
66
78
|
isError: true,
|
|
67
79
|
content: [
|
|
68
80
|
{
|
|
69
81
|
type: 'text',
|
|
70
|
-
text: `Error: ${error.message}`
|
|
82
|
+
text: `Error executing tool '${request.params.name}': ${error.message}`
|
|
71
83
|
}
|
|
72
84
|
]
|
|
73
85
|
};
|
|
@@ -109,8 +121,6 @@ Options:
|
|
|
109
121
|
|
|
110
122
|
This MCP server provides the following tools:
|
|
111
123
|
- web-search: Search the web using DuckDuckGo
|
|
112
|
-
- fetch-url: Fetch and extract content from a URL
|
|
113
|
-
- url-metadata: Extract metadata from a URL
|
|
114
124
|
- felo-search: Search using Felo AI for AI-generated responses
|
|
115
125
|
|
|
116
126
|
Created by @OEvortex
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@oevortex/ddg_search","version":"1.1.
|
|
1
|
+
{"name":"@oevortex/ddg_search","version":"1.1.4","description":"A Model Context Protocol server for web search using DuckDuckGo and IAsk AI","main":"src/index.js","module":"src/index.ts","exports":{".":{"import":"./src/index.js","default":"./src/index.js"}},"bin":{"ddg-search-mcp":"bin/cli.js","oevortex-ddg-search":"bin/cli.js"},"scripts":{"test":"echo \"Error: no test specified\" && exit 1","start":"node bin/cli.js","prepublishOnly":"npm run lint","lint":"echo \"No linting configured\"","build":"npx @smithery/cli build","dev":"npx @smithery/cli dev"},"publishConfig":{"access":"public"},"keywords":["mcp","model-context-protocol","duckduckgo","iask","search","web-search","ai-search","claude","ai","llm"],"author":"OEvortex","license":"Apache-2.0","type":"module","dependencies":{"@modelcontextprotocol/sdk":"^1.17.4","axios":"^1.8.4","cheerio":"^1.0.0","jsdom":"^26.1.0","smithery":"^0.5.2","turndown":"^7.2.2","uuid":"^9.0.1","ws":"^8.18.3"},"devDependencies":{"@types/node":"^24.3.0","tsx":"^4.20.4","typescript":"^5.9.2"}}
|
package/smithery.yaml
ADDED
package/src/index.js
CHANGED
|
@@ -1,82 +1,95 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
2
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
3
|
|
|
5
4
|
// Import tool definitions and handlers
|
|
6
5
|
import { searchToolDefinition, searchToolHandler } from './tools/searchTool.js';
|
|
7
|
-
import { fetchUrlToolDefinition, fetchUrlToolHandler } from './tools/fetchUrlTool.js';
|
|
8
|
-
import { metadataToolDefinition, metadataToolHandler } from './tools/metadataTool.js';
|
|
9
6
|
import { feloToolDefinition, feloToolHandler } from './tools/feloTool.js';
|
|
10
7
|
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
name: 'DuckDuckGo & Felo AI Search MCP',
|
|
15
|
-
description: 'A Model Context Protocol server for web search using DuckDuckGo and Felo AI',
|
|
16
|
-
version: '1.1.1'
|
|
17
|
-
}, {
|
|
18
|
-
capabilities: {
|
|
19
|
-
tools: {}
|
|
20
|
-
}
|
|
21
|
-
});
|
|
8
|
+
// Required: Export default createServer function for Smithery
|
|
9
|
+
export default function createServer({ config } = {}) {
|
|
10
|
+
console.log('Creating MCP server with latest SDK...');
|
|
22
11
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
feloToolDefinition
|
|
31
|
-
]
|
|
32
|
-
};
|
|
33
|
-
});
|
|
12
|
+
// Global variable to track available tools
|
|
13
|
+
const availableTools = [
|
|
14
|
+
searchToolDefinition,
|
|
15
|
+
feloToolDefinition
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
console.log('Available tools:', availableTools.map(t => t.name));
|
|
34
19
|
|
|
35
|
-
//
|
|
36
|
-
server
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
case 'fetch-url':
|
|
45
|
-
return await fetchUrlToolHandler(args);
|
|
46
|
-
|
|
47
|
-
case 'url-metadata':
|
|
48
|
-
return await metadataToolHandler(args);
|
|
49
|
-
|
|
50
|
-
case 'felo-search':
|
|
51
|
-
return await feloToolHandler(args);
|
|
52
|
-
|
|
53
|
-
default:
|
|
54
|
-
throw new Error(`Tool not found: ${name}`);
|
|
20
|
+
// Create the MCP server using the Server class
|
|
21
|
+
const server = new Server({
|
|
22
|
+
name: 'ddg-search-mcp',
|
|
23
|
+
version: '1.1.2'
|
|
24
|
+
}, {
|
|
25
|
+
capabilities: {
|
|
26
|
+
tools: {
|
|
27
|
+
listChanged: true
|
|
28
|
+
}
|
|
55
29
|
}
|
|
56
|
-
}
|
|
57
|
-
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Define available tools
|
|
33
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
34
|
+
console.log('Tools list requested, returning:', availableTools.length, 'tools');
|
|
58
35
|
return {
|
|
59
|
-
|
|
60
|
-
content: [
|
|
61
|
-
{
|
|
62
|
-
type: 'text',
|
|
63
|
-
text: `Error: ${error.message}`
|
|
64
|
-
}
|
|
65
|
-
]
|
|
36
|
+
tools: availableTools
|
|
66
37
|
};
|
|
67
|
-
}
|
|
68
|
-
});
|
|
38
|
+
});
|
|
69
39
|
|
|
70
|
-
//
|
|
71
|
-
async
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
40
|
+
// Handle tool execution
|
|
41
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
42
|
+
try {
|
|
43
|
+
const { name, arguments: args } = request.params;
|
|
44
|
+
console.log(`Tool call received: ${name} with args:`, args);
|
|
45
|
+
|
|
46
|
+
// Route to the appropriate tool handler
|
|
47
|
+
switch (name) {
|
|
48
|
+
case 'web-search':
|
|
49
|
+
return await searchToolHandler(args);
|
|
50
|
+
|
|
51
|
+
case 'felo-search':
|
|
52
|
+
return await feloToolHandler(args);
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`Tool not found: ${name}`);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(`Error handling ${request.params.name} tool call:`, error);
|
|
59
|
+
|
|
60
|
+
// Return proper tool execution error format
|
|
61
|
+
return {
|
|
62
|
+
isError: true,
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: `Error executing tool '${request.params.name}': ${error.message}`
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log('MCP server created successfully');
|
|
74
|
+
|
|
75
|
+
// Return the server instance (required for Smithery)
|
|
76
|
+
return server;
|
|
80
77
|
}
|
|
81
78
|
|
|
82
|
-
|
|
79
|
+
// Legacy standalone server support (for CLI usage)
|
|
80
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
81
|
+
async function main() {
|
|
82
|
+
try {
|
|
83
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
84
|
+
const server = createServer();
|
|
85
|
+
const transport = new StdioServerTransport();
|
|
86
|
+
await server.connect(transport);
|
|
87
|
+
console.error('WebSearch MCP server started and listening on stdio');
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Failed to start server:', error);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
main();
|
|
95
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
|
|
4
|
+
// Import tool definitions and handlers
|
|
5
|
+
import { searchToolDefinition, searchToolHandler } from './tools/searchTool.js';
|
|
6
|
+
import { iaskToolDefinition, iaskToolHandler } from './tools/iaskTool.js';
|
|
7
|
+
|
|
8
|
+
// Required: Export default createServer function for Smithery
|
|
9
|
+
export default function createServer({ config }: { config?: any } = {}) {
|
|
10
|
+
console.log('Creating MCP server with latest SDK...');
|
|
11
|
+
|
|
12
|
+
// Global variable to track available tools
|
|
13
|
+
const availableTools = [
|
|
14
|
+
searchToolDefinition,
|
|
15
|
+
iaskToolDefinition
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
console.log('Available tools:', availableTools.map(t => t.name));
|
|
19
|
+
|
|
20
|
+
// Create the MCP server using the Server class
|
|
21
|
+
const server = new Server({
|
|
22
|
+
name: 'ddg-search-mcp',
|
|
23
|
+
version: '1.1.2'
|
|
24
|
+
}, {
|
|
25
|
+
capabilities: {
|
|
26
|
+
tools: {
|
|
27
|
+
listChanged: true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Define available tools
|
|
33
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
34
|
+
console.log('Tools list requested, returning:', availableTools.length, 'tools');
|
|
35
|
+
return {
|
|
36
|
+
tools: availableTools
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Handle tool execution
|
|
41
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
42
|
+
try {
|
|
43
|
+
const { name, arguments: args } = request.params;
|
|
44
|
+
console.log(`Tool call received: ${name} with args:`, args);
|
|
45
|
+
|
|
46
|
+
// Route to the appropriate tool handler
|
|
47
|
+
switch (name) {
|
|
48
|
+
case 'web-search':
|
|
49
|
+
return await searchToolHandler(args);
|
|
50
|
+
|
|
51
|
+
case 'iask-search':
|
|
52
|
+
return await iaskToolHandler(args);
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
throw new Error(`Tool not found: ${name}`);
|
|
56
|
+
}
|
|
57
|
+
} catch (error: any) {
|
|
58
|
+
console.error(`Error handling ${request.params.name} tool call:`, error);
|
|
59
|
+
|
|
60
|
+
// Return proper tool execution error format
|
|
61
|
+
return {
|
|
62
|
+
isError: true,
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: `Error executing tool '${request.params.name}': ${error.message}`
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log('MCP server created successfully');
|
|
74
|
+
|
|
75
|
+
// Return the server instance (required for Smithery)
|
|
76
|
+
return server;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Optional: No configuration schema needed for this server
|
|
80
|
+
// export const configSchema = z.object({});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { searchIAsk, VALID_MODES, VALID_DETAIL_LEVELS } from '../utils/search_iask.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IAsk AI search tool definition
|
|
5
|
+
*/
|
|
6
|
+
export const iaskToolDefinition = {
|
|
7
|
+
name: 'iask-search',
|
|
8
|
+
title: 'IAsk AI Search',
|
|
9
|
+
description: 'AI-powered search using IAsk.ai. Retrieves comprehensive, AI-generated responses based on web content. Supports different search modes (question, academic, forums, wiki, thinking) and detail levels (concise, detailed, comprehensive). Ideal for getting well-researched answers to complex questions.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
query: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'The search query or question to ask. Supports natural language questions for comprehensive AI-generated responses.'
|
|
16
|
+
},
|
|
17
|
+
mode: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Search mode to use. Options: "question" (general questions), "academic" (scholarly/research), "forums" (community discussions), "wiki" (encyclopedia-style), "thinking" (deep analysis). Default is "question".',
|
|
20
|
+
enum: VALID_MODES,
|
|
21
|
+
default: 'question'
|
|
22
|
+
},
|
|
23
|
+
detailLevel: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'Level of detail in the response. Options: "concise" (brief), "detailed" (moderate), "comprehensive" (extensive). Default is null (standard response).',
|
|
26
|
+
enum: VALID_DETAIL_LEVELS
|
|
27
|
+
},
|
|
28
|
+
stream: {
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
description: 'Enable streaming mode to receive incremental results. Default is false.',
|
|
31
|
+
default: false
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
required: ['query']
|
|
35
|
+
},
|
|
36
|
+
annotations: {
|
|
37
|
+
readOnlyHint: true,
|
|
38
|
+
openWorldHint: false
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* IAsk AI search tool handler
|
|
44
|
+
* @param {Object} params - The tool parameters
|
|
45
|
+
* @returns {Promise<Object>} - The tool result
|
|
46
|
+
*/
|
|
47
|
+
export async function iaskToolHandler(params) {
|
|
48
|
+
const {
|
|
49
|
+
query,
|
|
50
|
+
mode = 'question',
|
|
51
|
+
detailLevel = null,
|
|
52
|
+
stream = false
|
|
53
|
+
} = params;
|
|
54
|
+
|
|
55
|
+
console.log(`Searching IAsk AI for: "${query}" (mode: ${mode}, detailLevel: ${detailLevel || 'default'}, stream: ${stream})`);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
if (stream) {
|
|
59
|
+
// For streaming responses, collect them and return
|
|
60
|
+
let fullResponse = '';
|
|
61
|
+
const chunks = [];
|
|
62
|
+
|
|
63
|
+
for await (const chunk of await searchIAsk(query, true, false, mode, detailLevel)) {
|
|
64
|
+
chunks.push(chunk);
|
|
65
|
+
fullResponse += chunk;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: fullResponse || 'No results found.'
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
} else {
|
|
77
|
+
// For non-streaming responses
|
|
78
|
+
const response = await searchIAsk(query, false, false, mode, detailLevel);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: response || 'No results found.'
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(`Error in IAsk search: ${error.message}`);
|
|
91
|
+
return {
|
|
92
|
+
isError: true,
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: 'text',
|
|
96
|
+
text: `Error searching IAsk: ${error.message}`
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|