agent-search-mcp 2.1.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 +80 -0
- package/LICENSE +207 -0
- package/README.md +480 -0
- package/dist/aggregation/dedup.js +102 -0
- package/dist/aggregation/format.js +60 -0
- package/dist/aggregation/index.js +3 -0
- package/dist/aggregation/scorer.js +110 -0
- package/dist/cli.js +169 -0
- package/dist/engines/baidu.js +56 -0
- package/dist/engines/bing.js +58 -0
- package/dist/engines/brave.js +33 -0
- package/dist/engines/duckduckgo.js +47 -0
- package/dist/engines/exa.js +46 -0
- package/dist/engines/index.js +25 -0
- package/dist/engines/sogou.js +132 -0
- package/dist/engines/tavily.js +33 -0
- package/dist/index.js +46 -0
- package/dist/infrastructure/cache.js +24 -0
- package/dist/infrastructure/config.js +18 -0
- package/dist/infrastructure/health.js +86 -0
- package/dist/infrastructure/html-utils.js +10 -0
- package/dist/infrastructure/http.js +66 -0
- package/dist/infrastructure/index.js +9 -0
- package/dist/infrastructure/logger.js +9 -0
- package/dist/infrastructure/rate-limiter.js +12 -0
- package/dist/infrastructure/security.js +158 -0
- package/dist/infrastructure/url-validator.js +33 -0
- package/dist/tools/capabilities.js +35 -0
- package/dist/tools/fetch-tools.js +200 -0
- package/dist/tools/free-extract.js +43 -0
- package/dist/tools/free-search-advanced.js +40 -0
- package/dist/tools/free-search.js +380 -0
- package/dist/tools/health.js +9 -0
- package/dist/types.js +1 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# Agent Search MCP
|
|
2
|
+
|
|
3
|
+
> 🔍 Free multi-source search for AI agents — multi-source verification, token savings, MCP native.
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](https://modelcontextprotocol.io)
|
|
8
|
+
[](https://github.com/lennney/agent-search-mcp)
|
|
9
|
+
|
|
10
|
+
**Works with Hermes, Claude Code, Cursor, Windsurf, OpenClaw, Codex, and any MCP-compatible client.**
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
[English](#why-agent-search-mcp) · [中文](README_zh.md) · [安装](#quick-start) · [工具文档](#tools) · [竞品对比](#competitor-comparison)
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Why Agent Search MCP
|
|
19
|
+
|
|
20
|
+
**AI agents need to search the internet. But existing solutions have problems:**
|
|
21
|
+
|
|
22
|
+
- **Tavily** — Great quality, but $0.01/search adds up fast. Monthly cost: $20-50+.
|
|
23
|
+
- **Exa** — Semantic search is powerful, but $50/month minimum.
|
|
24
|
+
- **Brave Search** — 2000 free queries/month, then $3/1000. Not enough for heavy use.
|
|
25
|
+
- **DDG MCP** — Single source, no verification, no dedup, results vary wildly.
|
|
26
|
+
- **open-websearch** — 13 engines, but 300MB+ dependency tree, no token optimization.
|
|
27
|
+
|
|
28
|
+
**Agent Search MCP solves this differently:**
|
|
29
|
+
|
|
30
|
+
1. **Free + high quality** — DuckDuckGo + Sogou as core engines, no API key needed
|
|
31
|
+
2. **Multi-source verification** — Results cross-checked across engines, each result gets a confidence score (1-3)
|
|
32
|
+
3. **Token optimization** — Title ≤100 chars, snippet ≤200 chars, dedup removes redundancy. Saves ~40-50% tokens.
|
|
33
|
+
4. **MCP native** — Built for Model Context Protocol from day one. Zero config, works out of the box.
|
|
34
|
+
5. **Self-hostable** — No data sent to third parties. Run it on your own VPS.
|
|
35
|
+
6. **Security built-in** — Prompt injection detection, output boundary markers, phishing URL filtering.
|
|
36
|
+
|
|
37
|
+
**Who is this for?**
|
|
38
|
+
|
|
39
|
+
- AI agent developers (Hermes, OpenClaw, custom agents)
|
|
40
|
+
- IDE users who want AI-powered search (Claude Code, Cursor, Windsurf)
|
|
41
|
+
- Anyone building MCP-compatible tools
|
|
42
|
+
- Users who need Chinese web search (Sogou integration)
|
|
43
|
+
|
|
44
|
+
**The math:** If you search 100 times/day, Tavily costs ~$1/day. Agent Search MCP costs $0. Over a year, that's $365 saved.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 为什么选择 Agent Search MCP
|
|
49
|
+
|
|
50
|
+
**AI Agent 需要搜索互联网。但现有方案都有问题:**
|
|
51
|
+
|
|
52
|
+
- **Tavily** — 质量好,但每次搜索 $0.01,月费 $20-50+
|
|
53
|
+
- **Exa** — 语义搜索强,但最低 $50/月
|
|
54
|
+
- **Brave Search** — 2000 次/月免费,之后 $3/1000,重度使用不够
|
|
55
|
+
- **DDG MCP** — 单源,无验证,无去重,结果质量不稳定
|
|
56
|
+
- **open-websearch** — 13 引擎,但 300MB+ 依赖,无 token 优化
|
|
57
|
+
|
|
58
|
+
**Agent Search MCP 的解决方案:**
|
|
59
|
+
|
|
60
|
+
1. **免费 + 高质量** — DuckDuckGo + Sogou 为核心,无需 API Key
|
|
61
|
+
2. **多源验证** — 跨引擎交叉验证,每个结果有置信度评分(1-3)
|
|
62
|
+
3. **Token 优化** — 标题 ≤100 字符,摘要 ≤200 字符,去重去除冗余。节省 ~40-50% token
|
|
63
|
+
4. **MCP 原生** — 基于 Model Context Protocol 构建,零配置开箱即用
|
|
64
|
+
5. **可自托管** — 数据不经过第三方,可在自有 VPS 运行
|
|
65
|
+
6. **内置安全** — Prompt 注入检测、输出边界标记、钓鱼 URL 过滤
|
|
66
|
+
|
|
67
|
+
**适用人群:**
|
|
68
|
+
|
|
69
|
+
- AI Agent 开发者(Hermes、OpenClaw、自定义 Agent)
|
|
70
|
+
- IDE 用户(Claude Code、Cursor、Windsurf)
|
|
71
|
+
- 构建 MCP 兼容工具的开发者
|
|
72
|
+
- 需要中文搜索的用户(搜狗集成)
|
|
73
|
+
|
|
74
|
+
**成本对比:** 如果每天搜索 100 次,Tavily 月费约 $30。Agent Search MCP 完全免费。一年省 $365。
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Competitor Comparison
|
|
79
|
+
|
|
80
|
+
| Feature | Agent Search MCP | Tavily | Exa | Brave Search | DDG MCP |
|
|
81
|
+
|---------|:---:|:---:|:---:|:---:|:---:|
|
|
82
|
+
| **Price** | Free | $0.01/search | $50/mo | $3/1000 | Free |
|
|
83
|
+
| **API Key** | Not required | Required | Required | Required | Required |
|
|
84
|
+
| **Multi-source** | ✅ 2-4 engines | ❌ Single | ❌ Single | ❌ Single | ❌ Single |
|
|
85
|
+
| **Confidence score** | ✅ 1-3 | ❌ | ❌ | ❌ | ❌ |
|
|
86
|
+
| **Deduplication** | ✅ URL + title | ❌ | ❌ | ❌ | ❌ |
|
|
87
|
+
| **Token optimization** | ✅ ~40-50% | ❌ | ❌ | ❌ | ❌ |
|
|
88
|
+
| **Chinese search** | ✅ Sogou | ❌ | ❌ | ❌ | ❌ |
|
|
89
|
+
| **MCP native** | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
90
|
+
| **Self-hostable** | ✅ | ❌ Cloud only | ❌ Cloud only | ❌ Cloud only | ✅ |
|
|
91
|
+
| **Progressive disclosure** | ✅ 3 tools | ❌ | ❌ | ❌ | ❌ |
|
|
92
|
+
| **Health monitoring** | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
93
|
+
| **Fallback chain** | ✅ Free→Paid | ❌ | ❌ | ❌ | ❌ |
|
|
94
|
+
| **Security** | ✅ Injection protection | ❌ | ❌ | ❌ | ❌ |
|
|
95
|
+
| **Dependencies** | 4 | 12+ | 15+ | 8 | 3 |
|
|
96
|
+
|
|
97
|
+
**Key differences:**
|
|
98
|
+
|
|
99
|
+
1. **Free by default** — No API key, no credit card, no limits. DuckDuckGo + Sogou work out of the box.
|
|
100
|
+
2. **Multi-source verification** — Results from multiple engines are cross-checked. Confidence score tells you how reliable a result is.
|
|
101
|
+
3. **Token optimization** — Smart truncation and dedup reduce token consumption by ~40-50%. This is crucial for cost-sensitive applications.
|
|
102
|
+
4. **Chinese support** — Sogou engine provides native Chinese web search. Not a translation layer.
|
|
103
|
+
5. **Progressive disclosure** — 3 tools at different complexity levels. Agents discover capabilities on-demand (Exa model).
|
|
104
|
+
6. **Security** — Built-in protection against prompt injection, phishing URLs, and output boundary markers.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Quick Start
|
|
109
|
+
|
|
110
|
+
### Prerequisites
|
|
111
|
+
|
|
112
|
+
- Node.js >= 18
|
|
113
|
+
- Python 3 with `ddgs` library:
|
|
114
|
+
```bash
|
|
115
|
+
pip install ddgs
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Install
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Option 1: npx (recommended)
|
|
122
|
+
npx agent-search-mcp
|
|
123
|
+
|
|
124
|
+
# Option 2: global install
|
|
125
|
+
npm install -g agent-search-mcp
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Platform Setup
|
|
129
|
+
|
|
130
|
+
<details>
|
|
131
|
+
<summary><b>Hermes</b></summary>
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
# ~/.hermes/config.yaml
|
|
135
|
+
mcp_servers:
|
|
136
|
+
agent-search:
|
|
137
|
+
command: npx
|
|
138
|
+
args: ["agent-search-mcp"]
|
|
139
|
+
```
|
|
140
|
+
</details>
|
|
141
|
+
|
|
142
|
+
<details>
|
|
143
|
+
<summary><b>Claude Code</b></summary>
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
// ~/.claude/mcp.json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"agent-search": {
|
|
150
|
+
"command": "npx",
|
|
151
|
+
"args": ["agent-search-mcp"]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
</details>
|
|
157
|
+
|
|
158
|
+
<details>
|
|
159
|
+
<summary><b>Cursor</b></summary>
|
|
160
|
+
|
|
161
|
+
```json
|
|
162
|
+
// .cursor/mcp.json
|
|
163
|
+
{
|
|
164
|
+
"mcpServers": {
|
|
165
|
+
"agent-search": {
|
|
166
|
+
"command": "npx",
|
|
167
|
+
"args": ["agent-search-mcp"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
</details>
|
|
173
|
+
|
|
174
|
+
<details>
|
|
175
|
+
<summary><b>Windsurf</b></summary>
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
// ~/.codeium/windsurf/mcp_config.json
|
|
179
|
+
{
|
|
180
|
+
"mcpServers": {
|
|
181
|
+
"agent-search": {
|
|
182
|
+
"command": "npx",
|
|
183
|
+
"args": ["agent-search-mcp"]
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
</details>
|
|
189
|
+
|
|
190
|
+
<details>
|
|
191
|
+
<summary><b>OpenClaw</b></summary>
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// openclaw.config.ts
|
|
195
|
+
{
|
|
196
|
+
mcpServers: {
|
|
197
|
+
"agent-search": {
|
|
198
|
+
command: "npx",
|
|
199
|
+
args: ["agent-search-mcp"]
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
</details>
|
|
205
|
+
|
|
206
|
+
<details>
|
|
207
|
+
<summary><b>Codex</b></summary>
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
// ~/.codex/mcp.json
|
|
211
|
+
{
|
|
212
|
+
"mcpServers": {
|
|
213
|
+
"agent-search": {
|
|
214
|
+
"command": "npx",
|
|
215
|
+
"args": ["agent-search-mcp"]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Features
|
|
225
|
+
|
|
226
|
+
- **Free by default** — DuckDuckGo + Sogou as core engines, no API key required. Brave and Tavily available as optional paid fallback.
|
|
227
|
+
- **Multi-source verification** — Results cross-checked across engines, each result gets a confidence score (1-3) based on how many sources returned it.
|
|
228
|
+
- **Token optimization** — Title truncation (≤100 chars), snippet truncation (≤200 chars), URL + title dedup. Saves ~40-50% tokens.
|
|
229
|
+
- **Progressive disclosure** — 3 tools at different complexity levels. `free_search` for quick queries, `free_search_advanced` for filtered search, `free_extract` for page content. Agents discover capabilities on-demand.
|
|
230
|
+
- **Fallback chain** — Free engines first, paid engines as backup. Automatic merge, dedup, and scoring.
|
|
231
|
+
- **Health monitoring** — Real-time provider health tracking. Unhealthy providers filtered automatically.
|
|
232
|
+
- **Security** — Prompt injection detection, output boundary markers, phishing URL filtering, and security metadata on every response.
|
|
233
|
+
- **CLI tool** — Use as a command-line tool for terminal search, web extraction, and HTTP server.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## CLI Usage
|
|
238
|
+
|
|
239
|
+
free-agent-search-mcp also works as a CLI tool.
|
|
240
|
+
|
|
241
|
+
### Install
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
npm install -g agent-search-mcp
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Search
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# Basic search
|
|
251
|
+
fasm search "TypeScript MCP server"
|
|
252
|
+
|
|
253
|
+
# With options
|
|
254
|
+
fasm search "query" --count 5 --engines bing,baidu
|
|
255
|
+
|
|
256
|
+
# JSON output
|
|
257
|
+
fasm search "query" --json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Extract Web Page
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
fasm extract "https://example.com"
|
|
264
|
+
fasm extract "https://example.com" --json
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Start HTTP Server
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
fasm serve --port 8080
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Help
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
fasm --help
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Tools
|
|
282
|
+
|
|
283
|
+
### `free_search`
|
|
284
|
+
|
|
285
|
+
Basic web search with multi-source verification.
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"query": "TypeScript MCP server",
|
|
290
|
+
"count": 5
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Returns:** Array of search results with confidence scores.
|
|
295
|
+
|
|
296
|
+
### `free_search_advanced`
|
|
297
|
+
|
|
298
|
+
Advanced search with filters.
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"query": "MCP server",
|
|
303
|
+
"count": 10,
|
|
304
|
+
"min_confidence": 2,
|
|
305
|
+
"time_range": "week",
|
|
306
|
+
"language": "zh",
|
|
307
|
+
"include_domains": ["github.com"],
|
|
308
|
+
"exclude_domains": ["reddit.com"]
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Parameters:**
|
|
313
|
+
- `min_confidence` (1-3): Only return results verified by N+ sources
|
|
314
|
+
- `time_range`: day, week, month, year
|
|
315
|
+
- `language`: auto, en, zh
|
|
316
|
+
- `include_domains`: Only search these domains
|
|
317
|
+
- `exclude_domains`: Exclude these domains
|
|
318
|
+
|
|
319
|
+
### `free_extract`
|
|
320
|
+
|
|
321
|
+
Extract full content from a URL as Markdown.
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"url": "https://example.com/article",
|
|
326
|
+
"max_length": 5000
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Returns:** Markdown content with metadata.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Resources
|
|
335
|
+
|
|
336
|
+
### `search://capabilities`
|
|
337
|
+
|
|
338
|
+
Returns a Markdown document describing all available tools and features. Agents can discover capabilities on-demand.
|
|
339
|
+
|
|
340
|
+
### `search://health`
|
|
341
|
+
|
|
342
|
+
Returns JSON with health status of each search provider. Useful for monitoring and debugging.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Configuration
|
|
347
|
+
|
|
348
|
+
### Environment Variables
|
|
349
|
+
|
|
350
|
+
| Variable | Description | Required |
|
|
351
|
+
|----------|-------------|----------|
|
|
352
|
+
| `BRAVE_API_KEY` | Brave Search API key (2000 free/month) | No |
|
|
353
|
+
| `TAVILY_API_KEY` | Tavily API key (1000 free/month) | No |
|
|
354
|
+
| `LOG_LEVEL` | Log level (info, debug) | No |
|
|
355
|
+
|
|
356
|
+
**Zero config works** — no API keys needed for basic search.
|
|
357
|
+
|
|
358
|
+
### With Paid Engines
|
|
359
|
+
|
|
360
|
+
Set environment variables to enable fallback to paid engines when free results are insufficient:
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
export BRAVE_API_KEY=your_key_here
|
|
364
|
+
export TAVILY_API_KEY=your_key_here
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Dependencies
|
|
370
|
+
|
|
371
|
+
| Dependency | License | Purpose |
|
|
372
|
+
|------------|---------|---------|
|
|
373
|
+
| @modelcontextprotocol/sdk | MIT | MCP protocol |
|
|
374
|
+
| zod | MIT | Schema validation |
|
|
375
|
+
| pino | MIT | Logging |
|
|
376
|
+
| yaml | ISC | Config parsing |
|
|
377
|
+
| ddgs (Python) | MIT | DuckDuckGo search backend (bypasses anti-bot) |
|
|
378
|
+
|
|
379
|
+
**Note:** `ddgs` is a Python library called via subprocess. It must be installed separately:
|
|
380
|
+
```bash
|
|
381
|
+
pip install ddgs
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Architecture
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
Agent
|
|
390
|
+
↓ MCP Protocol (stdio)
|
|
391
|
+
MCP Server
|
|
392
|
+
├── Tools Layer (progressive disclosure)
|
|
393
|
+
│ ├── free_search (default)
|
|
394
|
+
│ ├── free_search_advanced (optional)
|
|
395
|
+
│ └── free_extract (optional)
|
|
396
|
+
├── Aggregation Layer
|
|
397
|
+
│ ├── Top-1 Snippet merge
|
|
398
|
+
│ ├── URL + Title dedup
|
|
399
|
+
│ ├── Scoring + Confidence
|
|
400
|
+
│ └── Output truncation
|
|
401
|
+
├── Security Layer
|
|
402
|
+
│ ├── Prompt injection detection
|
|
403
|
+
│ ├── Output boundary markers
|
|
404
|
+
│ ├── Phishing URL filtering
|
|
405
|
+
│ └── Security metadata
|
|
406
|
+
├── Fallback Chain
|
|
407
|
+
│ ├── Phase 1: Free engines (DDG + Sogou)
|
|
408
|
+
│ └── Phase 2: Paid engines (Brave + Tavily)
|
|
409
|
+
└── Infrastructure
|
|
410
|
+
├── Cache (LRU, 60s TTL)
|
|
411
|
+
├── Rate Limiter (1s per provider)
|
|
412
|
+
├── Health Tracker
|
|
413
|
+
└── SSRF Protection
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Documentation / 文档
|
|
419
|
+
|
|
420
|
+
| Document | Description |
|
|
421
|
+
|----------|-------------|
|
|
422
|
+
| [PRD](docs/prd.md) | Product Requirements Document |
|
|
423
|
+
| [Architecture](docs/architecture.md) | Technical Architecture |
|
|
424
|
+
| [Plan](docs/plan.md) | Implementation Plan |
|
|
425
|
+
| [Review Results](docs/review-results.md) | 5-Team Review Results |
|
|
426
|
+
| [Fork Plan](docs/fork-plan.md) | Fork & Modification Plan |
|
|
427
|
+
| [CHANGELOG](CHANGELOG.md) | Version History |
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Development
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
# Clone
|
|
435
|
+
git clone https://github.com/lennney/agent-search-mcp.git
|
|
436
|
+
cd agent-search-mcp
|
|
437
|
+
|
|
438
|
+
# Install
|
|
439
|
+
npm install
|
|
440
|
+
|
|
441
|
+
# Build
|
|
442
|
+
npm run build
|
|
443
|
+
|
|
444
|
+
# Test
|
|
445
|
+
npm test
|
|
446
|
+
|
|
447
|
+
# Run
|
|
448
|
+
npm start
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Roadmap
|
|
454
|
+
|
|
455
|
+
- [ ] v0.1.0 — Initial release with DDG + Sogou
|
|
456
|
+
- [ ] v0.2.0 — Brave + Tavily fallback
|
|
457
|
+
- [ ] v0.3.0 — Health monitoring + rate limiting
|
|
458
|
+
- [ ] v1.0.0 — Stable release with documentation
|
|
459
|
+
- [ ] v1.1.0 — Plugin system for custom engines
|
|
460
|
+
- [ ] v2.0.0 — Browser-based extraction (Playwright)
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## License
|
|
465
|
+
|
|
466
|
+
[Apache License 2.0](LICENSE)
|
|
467
|
+
|
|
468
|
+
Based on [open-websearch](https://github.com/Aas-ee/open-websearch) by Aas-ee.
|
|
469
|
+
|
|
470
|
+
```
|
|
471
|
+
Copyright 2025 Open-WebSearch MCP Server Contributors
|
|
472
|
+
Based on open-websearch by Aas-ee (Apache 2.0).
|
|
473
|
+
Modified by Agent Search MCP Contributors.
|
|
474
|
+
Copyright 2026 Agent Search MCP Contributors
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Contributing
|
|
480
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export function normalizeUrl(url) {
|
|
2
|
+
try {
|
|
3
|
+
const u = new URL(url);
|
|
4
|
+
return `${u.hostname}${u.pathname.replace(/\/$/, '')}`.toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return url.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Provider-aware dedup: same provider only searches once.
|
|
12
|
+
* From ddgs: track which providers we've already queried.
|
|
13
|
+
*/
|
|
14
|
+
export function dedupByProvider(engines) {
|
|
15
|
+
// Map engine -> provider (e.g., 'ddg' -> 'bing', 'sogou' -> 'sogou')
|
|
16
|
+
const providerMap = {
|
|
17
|
+
duckduckgo: 'bing', // DDG uses Bing backend
|
|
18
|
+
sogou: 'sogou',
|
|
19
|
+
brave: 'brave',
|
|
20
|
+
tavily: 'tavily',
|
|
21
|
+
};
|
|
22
|
+
const seenProviders = new Set();
|
|
23
|
+
const uniqueEngines = [];
|
|
24
|
+
for (const engine of engines) {
|
|
25
|
+
const provider = providerMap[engine] || engine;
|
|
26
|
+
if (!seenProviders.has(provider)) {
|
|
27
|
+
seenProviders.add(provider);
|
|
28
|
+
uniqueEngines.push(engine);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return uniqueEngines;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* URL dedup with frequency counting.
|
|
35
|
+
* From ddgs: track how many engines returned each URL.
|
|
36
|
+
* Keep the item with longer body (richer content).
|
|
37
|
+
*/
|
|
38
|
+
export function dedupByUrl(results) {
|
|
39
|
+
const seen = new Map();
|
|
40
|
+
const frequencies = new Map();
|
|
41
|
+
for (const r of results) {
|
|
42
|
+
const key = normalizeUrl(r.url);
|
|
43
|
+
frequencies.set(key, (frequencies.get(key) || 0) + 1);
|
|
44
|
+
if (!seen.has(key)) {
|
|
45
|
+
seen.set(key, r);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// From ddgs: keep the item with longer body (richer content)
|
|
49
|
+
const existing = seen.get(key);
|
|
50
|
+
if ((r.snippet?.length || 0) > (existing.snippet?.length || 0)) {
|
|
51
|
+
seen.set(key, r);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { results: Array.from(seen.values()), frequencies };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Title dedup with Jaccard similarity.
|
|
59
|
+
*/
|
|
60
|
+
export function dedupByTitle(results, threshold = 0.85) {
|
|
61
|
+
const kept = [];
|
|
62
|
+
for (const r of results) {
|
|
63
|
+
const isDuplicate = kept.some(k => jaccard(k.title, r.title) > threshold);
|
|
64
|
+
if (!isDuplicate)
|
|
65
|
+
kept.push(r);
|
|
66
|
+
}
|
|
67
|
+
return kept;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Filter low-quality results.
|
|
71
|
+
* From ddgs: post_extract_results filters ads and invalid results.
|
|
72
|
+
*/
|
|
73
|
+
export function filterLowQuality(results) {
|
|
74
|
+
return results.filter(r => {
|
|
75
|
+
// Filter empty snippets
|
|
76
|
+
if (!r.snippet || r.snippet.length < 20)
|
|
77
|
+
return false;
|
|
78
|
+
// Filter DDG ads
|
|
79
|
+
if (r.url.includes('y.js?') || r.url.includes('/ad/'))
|
|
80
|
+
return false;
|
|
81
|
+
// Filter invalid URLs
|
|
82
|
+
if (!r.url.startsWith('http'))
|
|
83
|
+
return false;
|
|
84
|
+
// Filter DDG ad redirects
|
|
85
|
+
if (r.url.includes('duckduckgo.com/y.js'))
|
|
86
|
+
return false;
|
|
87
|
+
// Filter search engine internal links
|
|
88
|
+
if (r.url.includes('sogou.com/link'))
|
|
89
|
+
return false;
|
|
90
|
+
// Filter Wikipedia categories (low quality)
|
|
91
|
+
if (r.url.includes('wikipedia.org/wiki/Category:'))
|
|
92
|
+
return false;
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function jaccard(a, b) {
|
|
97
|
+
const setA = new Set(a.split(/\s+/));
|
|
98
|
+
const setB = new Set(b.split(/\s+/));
|
|
99
|
+
const intersection = new Set([...setA].filter(x => setB.has(x)));
|
|
100
|
+
const union = new Set([...setA, ...setB]);
|
|
101
|
+
return union.size > 0 ? intersection.size / union.size : 0;
|
|
102
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { processResultSecurity, getSecurityNote, wrapWithBoundaryMarkers } from '../infrastructure/security.js';
|
|
2
|
+
/**
|
|
3
|
+
* Format search results with security processing.
|
|
4
|
+
*
|
|
5
|
+
* Security features:
|
|
6
|
+
* - Snippet injection detection and marking
|
|
7
|
+
* - URL phishing detection
|
|
8
|
+
* - Boundary markers for agent clarity
|
|
9
|
+
* - Security metadata per result
|
|
10
|
+
*/
|
|
11
|
+
export function formatResults(results) {
|
|
12
|
+
// Process security for each result
|
|
13
|
+
const secured = results.map(r => processResultSecurity(r));
|
|
14
|
+
return {
|
|
15
|
+
results: secured.map(r => ({
|
|
16
|
+
title: r.title.slice(0, 100),
|
|
17
|
+
url: r.url,
|
|
18
|
+
snippet: r.snippet.slice(0, 200),
|
|
19
|
+
confidence: r.confidence,
|
|
20
|
+
// Only include security details if threats detected
|
|
21
|
+
...(r.security.injectionDetected || !r.security.urlSafe ? {
|
|
22
|
+
security: {
|
|
23
|
+
injection_detected: r.security.injectionDetected,
|
|
24
|
+
url_safe: r.security.urlSafe,
|
|
25
|
+
threats: r.security.threats,
|
|
26
|
+
warnings: r.security.warnings,
|
|
27
|
+
},
|
|
28
|
+
} : {}),
|
|
29
|
+
})),
|
|
30
|
+
meta: {
|
|
31
|
+
total: results.length,
|
|
32
|
+
high_confidence: results.filter(r => r.confidence >= 2).length,
|
|
33
|
+
engines: [...new Set(results.flatMap(r => r.engines || [r.source]))],
|
|
34
|
+
},
|
|
35
|
+
security_note: getSecurityNote(),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format results as XML boundary-marked output.
|
|
40
|
+
* Useful for agents that need clear data/instruction separation.
|
|
41
|
+
*/
|
|
42
|
+
export function formatResultsXml(results) {
|
|
43
|
+
const header = [
|
|
44
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
45
|
+
'<search-response>',
|
|
46
|
+
` <security-note>${getSecurityNote()}</security-note>`,
|
|
47
|
+
' <results>',
|
|
48
|
+
].join('\n');
|
|
49
|
+
const body = results.map(r => {
|
|
50
|
+
const secured = processResultSecurity(r);
|
|
51
|
+
return wrapWithBoundaryMarkers({
|
|
52
|
+
title: secured.title.slice(0, 100),
|
|
53
|
+
url: secured.url,
|
|
54
|
+
snippet: secured.snippet.slice(0, 200),
|
|
55
|
+
confidence: secured.confidence,
|
|
56
|
+
});
|
|
57
|
+
}).join('\n');
|
|
58
|
+
const footer = ' </results>\n</search-response>';
|
|
59
|
+
return [header, body, footer].join('\n');
|
|
60
|
+
}
|