duckduckgo-websearch 0.1.7 → 2.0.2
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/README.md +142 -152
- package/build/cookieJar.d.ts +13 -0
- package/build/cookieJar.d.ts.map +1 -0
- package/build/cookieJar.js +122 -0
- package/build/cookieJar.js.map +1 -0
- package/build/duckduckgoSearcher.d.ts +5 -0
- package/build/duckduckgoSearcher.d.ts.map +1 -1
- package/build/duckduckgoSearcher.js +188 -99
- package/build/duckduckgoSearcher.js.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/types.d.ts +4 -0
- package/build/types.d.ts.map +1 -1
- package/build/types.js +9 -0
- package/build/types.js.map +1 -1
- package/package.json +66 -68
package/README.md
CHANGED
|
@@ -1,243 +1,233 @@
|
|
|
1
|
-
# DuckDuckGo Search MCP Server
|
|
1
|
+
# DuckDuckGo Search MCP Server
|
|
2
2
|
|
|
3
|
-
A Model Context Protocol (MCP) server that provides web search
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that provides web search and content fetching via DuckDuckGo. No API key required.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Web Search
|
|
8
|
-
- **Content Fetching
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
|
|
7
|
+
- **Web Search** — Multi-page results (supports 10 ~ 100+ results via automatic pagination)
|
|
8
|
+
- **Content Fetching** — Fetch and parse any webpage to clean text
|
|
9
|
+
- **Bot Detection & Retry** — Automatically retries up to 3 times on DDG bot challenges
|
|
10
|
+
- **Session Cookie Jar** — Persists DDG session cookies in-memory across requests
|
|
11
|
+
- **Advanced Query Syntax** — Full support for DDG/Google-style operators (`site:`, `OR`, `intitle:`, etc.)
|
|
12
|
+
- **Zero-Click Results** — Instant answer cards (e.g. search `ip`, `weather`)
|
|
13
|
+
- **Zero Dependencies at Runtime** — Only `cheerio` + `@modelcontextprotocol/sdk`
|
|
14
|
+
- **Node.js & Bun** — Works with both runtimes (Node ≥ 18)
|
|
15
|
+
|
|
16
|
+
---
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
19
|
-
###
|
|
20
|
+
### As MCP Server (global CLI)
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
|
-
# Install globally via npm
|
|
23
23
|
npm install -g duckduckgo-websearch
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
pnpm add -g duckduckgo-websearch
|
|
27
|
-
|
|
28
|
-
# Or using yarn
|
|
29
|
-
yarn global add duckduckgo-websearch
|
|
24
|
+
# or
|
|
25
|
+
npx duckduckgo-websearch
|
|
30
26
|
```
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
- `npx duckduckgo-websearch`
|
|
34
|
-
- `npx ddg-websearch`
|
|
35
|
-
- `duckduckgo-websearch`
|
|
36
|
-
- `ddg-websearch` (short alias)
|
|
37
|
-
|
|
38
|
-
### Local Installation
|
|
28
|
+
### As npm Library
|
|
39
29
|
|
|
40
30
|
```bash
|
|
41
|
-
|
|
42
|
-
git clone https://github.com/HeiSir2014/duckduckgo-mcp-server
|
|
43
|
-
cd duckduckgo-mcp-server
|
|
44
|
-
npm install
|
|
45
|
-
npm run build
|
|
31
|
+
npm install duckduckgo-websearch
|
|
46
32
|
```
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
### Global Usage
|
|
34
|
+
---
|
|
51
35
|
|
|
52
|
-
|
|
36
|
+
## MCP Server Usage
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
# Start the server
|
|
56
|
-
npx duckduckgo-websearch
|
|
38
|
+
### Claude Desktop
|
|
57
39
|
|
|
58
|
-
|
|
59
|
-
npx ddg-websearch
|
|
40
|
+
Edit your Claude Desktop config:
|
|
60
41
|
|
|
61
|
-
|
|
62
|
-
|
|
42
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
43
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
63
44
|
|
|
64
|
-
# Or using the short alias
|
|
65
|
-
ddg-websearch
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Running with Claude Desktop
|
|
69
|
-
|
|
70
|
-
1. Download [Claude Desktop](https://claude.ai/download)
|
|
71
|
-
2. Create or edit your Claude Desktop configuration:
|
|
72
|
-
- On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
73
|
-
- On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
74
|
-
|
|
75
|
-
#### For Global Installation:
|
|
76
45
|
```json
|
|
77
46
|
{
|
|
78
47
|
"mcpServers": {
|
|
79
48
|
"ddg-search": {
|
|
80
|
-
"command": "npx
|
|
49
|
+
"command": "npx",
|
|
50
|
+
"args": ["duckduckgo-websearch"]
|
|
81
51
|
}
|
|
82
52
|
}
|
|
83
53
|
}
|
|
84
54
|
```
|
|
85
55
|
|
|
86
|
-
|
|
56
|
+
Or with a local build:
|
|
57
|
+
|
|
87
58
|
```json
|
|
88
59
|
{
|
|
89
60
|
"mcpServers": {
|
|
90
61
|
"ddg-search": {
|
|
91
62
|
"command": "node",
|
|
92
|
-
"args": ["path/to/duckduckgo-mcp-server/build/index.js"]
|
|
63
|
+
"args": ["/path/to/duckduckgo-mcp-server/build/index.js"]
|
|
93
64
|
}
|
|
94
65
|
}
|
|
95
66
|
}
|
|
96
67
|
```
|
|
97
68
|
|
|
98
|
-
|
|
69
|
+
### Other MCP Clients
|
|
99
70
|
|
|
100
|
-
|
|
71
|
+
Any MCP-compatible client (Cursor, Cline, Continue, etc.) can connect via stdio transport using the same command above.
|
|
101
72
|
|
|
102
|
-
|
|
73
|
+
---
|
|
103
74
|
|
|
104
|
-
|
|
105
|
-
# Install dependencies
|
|
106
|
-
npm install
|
|
75
|
+
## MCP Tools
|
|
107
76
|
|
|
108
|
-
|
|
109
|
-
npm run build
|
|
77
|
+
### `search`
|
|
110
78
|
|
|
111
|
-
|
|
112
|
-
npm run dev
|
|
79
|
+
Search DuckDuckGo and return paginated results.
|
|
113
80
|
|
|
114
|
-
|
|
115
|
-
|
|
81
|
+
| Parameter | Type | Default | Description |
|
|
82
|
+
|-----------|------|---------|-------------|
|
|
83
|
+
| `query` | `string` | required | Search query. Supports advanced syntax (see below) |
|
|
84
|
+
| `max_results` | `integer` | `25` | Number of results to return. Triggers automatic pagination when > 10 |
|
|
116
85
|
|
|
117
|
-
|
|
118
|
-
npm test
|
|
119
|
-
```
|
|
86
|
+
**Advanced Query Syntax** (DDG supports Google-style operators):
|
|
120
87
|
|
|
121
|
-
|
|
88
|
+
| Syntax | Example | Effect |
|
|
89
|
+
|--------|---------|--------|
|
|
90
|
+
| `site:domain` | `site:github.com python` | Restrict to a domain |
|
|
91
|
+
| `site:a.com OR site:b.com` | `site:docs.python.org OR site:stackoverflow.com` | Multiple domains |
|
|
92
|
+
| `"exact phrase"` | `"model context protocol"` | Exact match |
|
|
93
|
+
| `-word` | `python -snake` | Exclude keyword |
|
|
94
|
+
| `intitle:word` | `intitle:tutorial python` | Match in page title |
|
|
95
|
+
| `filetype:ext` | `filetype:pdf machine learning` | Filter by file type |
|
|
96
|
+
| `OR` / `AND` | `python OR javascript async` | Boolean operators |
|
|
122
97
|
|
|
123
|
-
|
|
98
|
+
**Response format:**
|
|
124
99
|
|
|
125
|
-
```typescript
|
|
126
|
-
async function search(query: string, max_results?: number): Promise<string>
|
|
127
100
|
```
|
|
101
|
+
Found 25 search results:
|
|
128
102
|
|
|
129
|
-
|
|
103
|
+
1. Page Title
|
|
104
|
+
URL: https://example.com/page
|
|
105
|
+
Summary: Brief description of the page content...
|
|
130
106
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- `max_results`: Maximum number of results to return (default: 10)
|
|
107
|
+
2. ...
|
|
108
|
+
```
|
|
134
109
|
|
|
135
|
-
|
|
136
|
-
Formatted string containing search results with titles, URLs, and snippets.
|
|
110
|
+
---
|
|
137
111
|
|
|
138
|
-
###
|
|
112
|
+
### `fetch_content`
|
|
139
113
|
|
|
140
|
-
|
|
141
|
-
async function fetch_content(url: string, max_length?: number): Promise<string>
|
|
142
|
-
```
|
|
114
|
+
Fetch and parse a webpage to clean, LLM-readable text.
|
|
143
115
|
|
|
144
|
-
|
|
116
|
+
| Parameter | Type | Default | Description |
|
|
117
|
+
|-----------|------|---------|-------------|
|
|
118
|
+
| `url` | `string` | required | Webpage URL to fetch |
|
|
119
|
+
| `max_content_length` | `integer` | `8000` | Maximum characters to return |
|
|
145
120
|
|
|
146
|
-
|
|
147
|
-
- `url`: The webpage URL to fetch content from
|
|
148
|
-
- `max_length`: Maximum content length to return (default: 8000)
|
|
121
|
+
---
|
|
149
122
|
|
|
150
|
-
|
|
151
|
-
Cleaned and formatted text content from the webpage.
|
|
123
|
+
## Library Usage (Node.js / Bun)
|
|
152
124
|
|
|
153
|
-
|
|
125
|
+
```typescript
|
|
126
|
+
import { WebSearch, WebFetcher } from 'duckduckgo-websearch';
|
|
154
127
|
|
|
155
|
-
|
|
128
|
+
// Search
|
|
129
|
+
const searcher = new WebSearch();
|
|
156
130
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
- Automatic queue management and wait times
|
|
131
|
+
// Basic search — returns up to 10 results (1 page)
|
|
132
|
+
const results = await searcher.search('claude anthropic');
|
|
160
133
|
|
|
161
|
-
|
|
134
|
+
// Request more results — auto-paginates across multiple DDG pages
|
|
135
|
+
const results = await searcher.search('python tutorial', { maxResults: 50 });
|
|
162
136
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
- Truncates long content appropriately
|
|
137
|
+
// Advanced query syntax works natively in the query string
|
|
138
|
+
const results = await searcher.search('site:github.com mcp server typescript');
|
|
139
|
+
const results = await searcher.search('site:docs.python.org OR site:realpython.com async await');
|
|
167
140
|
|
|
168
|
-
|
|
141
|
+
// Format for LLM consumption
|
|
142
|
+
console.log(searcher.formatResultsForLLM(results));
|
|
169
143
|
|
|
170
|
-
|
|
171
|
-
|
|
144
|
+
// Fetch webpage content
|
|
145
|
+
const fetcher = new WebFetcher();
|
|
146
|
+
const content = await fetcher.fetchAndParse('https://example.com', 8000);
|
|
147
|
+
```
|
|
172
148
|
|
|
173
|
-
|
|
149
|
+
### SearchResult type
|
|
174
150
|
|
|
175
|
-
|
|
151
|
+
```typescript
|
|
152
|
+
interface SearchResult {
|
|
153
|
+
title: string;
|
|
154
|
+
link: string;
|
|
155
|
+
snippet: string;
|
|
156
|
+
position: number;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
176
159
|
|
|
177
|
-
|
|
178
|
-
- `cheerio`: Server-side jQuery implementation for HTML parsing
|
|
179
|
-
- Node.js 内置 `fetch` API:用于 HTTP 请求(无需额外依赖)
|
|
160
|
+
### Error handling
|
|
180
161
|
|
|
181
|
-
|
|
162
|
+
```typescript
|
|
163
|
+
import { WebSearch, SearchError } from 'duckduckgo-websearch';
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const results = await searcher.search('query');
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (err instanceof SearchError) {
|
|
169
|
+
console.error(err.code); // 'BOT_DETECTED' | 'HTTP_ERROR' | 'TIMEOUT' | 'UNKNOWN'
|
|
170
|
+
console.error(err.message);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
182
174
|
|
|
183
|
-
|
|
175
|
+
`SearchError` codes:
|
|
184
176
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
| Code | Meaning |
|
|
178
|
+
|------|---------|
|
|
179
|
+
| `BOT_DETECTED` | DDG bot challenge triggered after 3 retry attempts |
|
|
180
|
+
| `HTTP_ERROR` | Non-2xx HTTP response |
|
|
181
|
+
| `TIMEOUT` | Request timed out (30s limit) |
|
|
182
|
+
| `UNKNOWN` | Unexpected failure |
|
|
189
183
|
|
|
190
|
-
|
|
184
|
+
---
|
|
191
185
|
|
|
192
|
-
|
|
186
|
+
## Development
|
|
193
187
|
|
|
194
188
|
```bash
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
npm run version:minor # 0.1.1 -> 0.2.0
|
|
198
|
-
npm run version:major # 0.1.1 -> 1.0.0
|
|
199
|
-
```
|
|
189
|
+
git clone https://github.com/HeiSir2014/duckduckgo-mcp-server
|
|
190
|
+
cd duckduckgo-mcp-server
|
|
200
191
|
|
|
201
|
-
|
|
192
|
+
npm install
|
|
193
|
+
npm run build # compile TypeScript → build/
|
|
202
194
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
npm run publish:check
|
|
195
|
+
# Run example test (Bun)
|
|
196
|
+
bun example/test.ts
|
|
206
197
|
|
|
207
|
-
#
|
|
208
|
-
|
|
198
|
+
# Run with Node
|
|
199
|
+
node -e "require('./build/index.js')"
|
|
209
200
|
```
|
|
210
201
|
|
|
211
|
-
|
|
202
|
+
---
|
|
212
203
|
|
|
213
|
-
|
|
214
|
-
- [ ] Build succeeds (`npm run build`)
|
|
215
|
-
- [ ] Version updated (`npm version [patch|minor|major]`)
|
|
216
|
-
- [ ] README updated
|
|
217
|
-
- [ ] CHANGELOG updated
|
|
204
|
+
## Architecture
|
|
218
205
|
|
|
219
|
-
|
|
206
|
+
```
|
|
207
|
+
src/
|
|
208
|
+
├── index.ts # MCP server entry, tool definitions
|
|
209
|
+
├── duckduckgoSearcher.ts # Search logic: fetch, bot detection, retry, pagination
|
|
210
|
+
├── cookieJar.ts # In-memory cookie jar for DDG session persistence
|
|
211
|
+
├── webContentFetcher.ts # Webpage fetch + text extraction
|
|
212
|
+
├── rateLimiter.ts # Token-bucket rate limiter
|
|
213
|
+
└── types.ts # Shared types (SearchResult, SearchOptions, SearchError)
|
|
214
|
+
```
|
|
220
215
|
|
|
221
|
-
|
|
216
|
+
**Pagination mechanism** — DDG HTML endpoint returns 10 results per page with a `vqd` session token embedded in the "Next" form. When `maxResults > 10`, the searcher chains page requests using `vqd` and the form parameters (`s`, `dc`) extracted from each page's nav-link.
|
|
222
217
|
|
|
223
|
-
|
|
224
|
-
- Enhanced content parsing options
|
|
225
|
-
- Caching layer for frequently accessed content
|
|
226
|
-
- Additional rate limiting strategies
|
|
227
|
-
- Better error recovery mechanisms
|
|
218
|
+
**Bot detection** — On each page fetch the searcher checks for missing result containers (`.serp__results`) and known challenge keywords. On detection it awaits the report ping (`/t/sl_h`) which warms the session, then retries. After 3 failures it throws `SearchError('BOT_DETECTED')`.
|
|
228
219
|
|
|
229
|
-
|
|
220
|
+
---
|
|
230
221
|
|
|
231
|
-
|
|
222
|
+
## Rate Limits
|
|
232
223
|
|
|
233
|
-
|
|
224
|
+
| Operation | Limit |
|
|
225
|
+
|-----------|-------|
|
|
226
|
+
| Search | 30 requests / minute |
|
|
227
|
+
| Content Fetch | 20 requests / minute |
|
|
234
228
|
|
|
235
|
-
|
|
229
|
+
---
|
|
236
230
|
|
|
237
|
-
|
|
238
|
-
- **Performance**: Generally faster startup and execution times
|
|
239
|
-
- **Ecosystem**: Access to the extensive npm ecosystem
|
|
240
|
-
- **Compatibility**: Better integration with Node.js-based toolchains
|
|
241
|
-
- **Global CLI**: Easy global installation and usage
|
|
231
|
+
## License
|
|
242
232
|
|
|
243
|
-
|
|
233
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare class CookieJar {
|
|
2
|
+
private store;
|
|
3
|
+
constructor(seeds?: Record<string, string[]>);
|
|
4
|
+
/** Parse Set-Cookie headers from a response and store them */
|
|
5
|
+
setCookies(headers: Headers, requestUrl: string): void;
|
|
6
|
+
/** Build the Cookie header string for a given URL */
|
|
7
|
+
getCookieHeader(url: string): string;
|
|
8
|
+
private isExpired;
|
|
9
|
+
private hostMatches;
|
|
10
|
+
}
|
|
11
|
+
export declare const cookieJar: CookieJar;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=cookieJar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookieJar.d.ts","sourceRoot":"","sources":["../src/cookieJar.ts"],"names":[],"mappings":"AAQA,cAAM,SAAS;IACb,OAAO,CAAC,KAAK,CAA+C;gBAIhD,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAM;IAmBhD,8DAA8D;IAC9D,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IA4DtD,qDAAqD;IACrD,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAgBpC,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,WAAW;CAOpB;AAKD,eAAO,MAAM,SAAS,WAAkB,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cookieJar = void 0;
|
|
4
|
+
class CookieJar {
|
|
5
|
+
// store key = normalized domain (no leading dot)
|
|
6
|
+
// inner key = cookie name
|
|
7
|
+
constructor(seeds = {}) {
|
|
8
|
+
this.store = new Map();
|
|
9
|
+
for (const [domain, cookies] of Object.entries(seeds)) {
|
|
10
|
+
const normalizedDomain = domain.replace(/^\./, '');
|
|
11
|
+
for (const cookie of cookies) {
|
|
12
|
+
const [name, ...rest] = cookie.split('=');
|
|
13
|
+
const value = rest.join('=');
|
|
14
|
+
if (!this.store.has(normalizedDomain)) {
|
|
15
|
+
this.store.set(normalizedDomain, new Map());
|
|
16
|
+
}
|
|
17
|
+
this.store.get(normalizedDomain).set(name.trim(), {
|
|
18
|
+
value: value.trim(),
|
|
19
|
+
path: '/',
|
|
20
|
+
domain: normalizedDomain,
|
|
21
|
+
hostOnly: false,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Parse Set-Cookie headers from a response and store them */
|
|
27
|
+
setCookies(headers, requestUrl) {
|
|
28
|
+
const requestHost = new URL(requestUrl).hostname;
|
|
29
|
+
// Headers.getSetCookie() is available in modern environments; fall back to get()
|
|
30
|
+
let setCookieLines = [];
|
|
31
|
+
if (typeof headers.getSetCookie === 'function') {
|
|
32
|
+
setCookieLines = headers.getSetCookie();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const raw = headers.get('set-cookie');
|
|
36
|
+
if (raw)
|
|
37
|
+
setCookieLines = [raw];
|
|
38
|
+
}
|
|
39
|
+
for (const line of setCookieLines) {
|
|
40
|
+
const parts = line.split(';').map((p) => p.trim());
|
|
41
|
+
const [nameValue, ...attrs] = parts;
|
|
42
|
+
const eqIdx = nameValue.indexOf('=');
|
|
43
|
+
if (eqIdx === -1)
|
|
44
|
+
continue;
|
|
45
|
+
const name = nameValue.slice(0, eqIdx).trim();
|
|
46
|
+
const value = nameValue.slice(eqIdx + 1).trim();
|
|
47
|
+
let expires;
|
|
48
|
+
let path = '/';
|
|
49
|
+
let domain = '';
|
|
50
|
+
let hostOnly = true;
|
|
51
|
+
for (const attr of attrs) {
|
|
52
|
+
const lower = attr.toLowerCase();
|
|
53
|
+
if (lower.startsWith('expires=')) {
|
|
54
|
+
const d = new Date(attr.slice('expires='.length));
|
|
55
|
+
if (!isNaN(d.getTime()))
|
|
56
|
+
expires = d;
|
|
57
|
+
}
|
|
58
|
+
else if (lower.startsWith('max-age=')) {
|
|
59
|
+
const secs = parseInt(attr.slice('max-age='.length), 10);
|
|
60
|
+
if (!isNaN(secs)) {
|
|
61
|
+
expires = new Date(Date.now() + secs * 1000);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (lower.startsWith('path=')) {
|
|
65
|
+
path = attr.slice('path='.length) || '/';
|
|
66
|
+
}
|
|
67
|
+
else if (lower.startsWith('domain=')) {
|
|
68
|
+
const raw = attr.slice('domain='.length).replace(/^\./, '');
|
|
69
|
+
if (raw) {
|
|
70
|
+
domain = raw;
|
|
71
|
+
hostOnly = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const normalizedDomain = domain || requestHost;
|
|
76
|
+
if (!this.store.has(normalizedDomain)) {
|
|
77
|
+
this.store.set(normalizedDomain, new Map());
|
|
78
|
+
}
|
|
79
|
+
this.store.get(normalizedDomain).set(name, {
|
|
80
|
+
value,
|
|
81
|
+
expires,
|
|
82
|
+
path,
|
|
83
|
+
domain: normalizedDomain,
|
|
84
|
+
hostOnly,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Build the Cookie header string for a given URL */
|
|
89
|
+
getCookieHeader(url) {
|
|
90
|
+
const { hostname, pathname } = new URL(url);
|
|
91
|
+
const matches = [];
|
|
92
|
+
for (const [, cookieMap] of this.store) {
|
|
93
|
+
for (const [name, entry] of cookieMap) {
|
|
94
|
+
if (this.isExpired(entry))
|
|
95
|
+
continue;
|
|
96
|
+
if (!this.hostMatches(entry.domain, entry.hostOnly, hostname))
|
|
97
|
+
continue;
|
|
98
|
+
if (!pathname.startsWith(entry.path))
|
|
99
|
+
continue;
|
|
100
|
+
matches.push(`${name}=${entry.value}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return matches.join('; ');
|
|
104
|
+
}
|
|
105
|
+
isExpired(entry) {
|
|
106
|
+
if (!entry.expires)
|
|
107
|
+
return false;
|
|
108
|
+
return entry.expires.getTime() < Date.now();
|
|
109
|
+
}
|
|
110
|
+
hostMatches(cookieDomain, hostOnly, requestHost) {
|
|
111
|
+
if (hostOnly) {
|
|
112
|
+
return cookieDomain === requestHost;
|
|
113
|
+
}
|
|
114
|
+
// domain cookie: match exact or any subdomain
|
|
115
|
+
return requestHost === cookieDomain || requestHost.endsWith(`.${cookieDomain}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Empty singleton — kl locale cookie must NOT be pre-seeded.
|
|
119
|
+
// Sending kl=cn-zh suppresses the nav-link/vqd that DDG embeds in the first page,
|
|
120
|
+
// breaking pagination. Locale is controlled via Accept-Language header instead.
|
|
121
|
+
exports.cookieJar = new CookieJar();
|
|
122
|
+
//# sourceMappingURL=cookieJar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookieJar.js","sourceRoot":"","sources":["../src/cookieJar.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,11 @@ export declare class DuckDuckGoSearcher {
|
|
|
7
7
|
private rateLimiter;
|
|
8
8
|
constructor();
|
|
9
9
|
formatResultsForLLM(results: SearchResult[]): string;
|
|
10
|
+
private extractNextPageParams;
|
|
11
|
+
private isBotDetected;
|
|
12
|
+
private parseResults;
|
|
13
|
+
private fetchPage;
|
|
14
|
+
private fireReportPing;
|
|
10
15
|
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
11
16
|
}
|
|
12
17
|
//# sourceMappingURL=duckduckgoSearcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duckduckgoSearcher.d.ts","sourceRoot":"","sources":["../src/duckduckgoSearcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"duckduckgoSearcher.d.ts","sourceRoot":"","sources":["../src/duckduckgoSearcher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAe,MAAM,SAAS,CAAC;AAInE,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IACtE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAmC;IAC1E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAkB7B;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CASjC;IAEL,OAAO,CAAC,WAAW,CAAc;;IAMjC,mBAAmB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM;IAkBpD,OAAO,CAAC,qBAAqB;IAiC7B,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,YAAY;YAuDN,SAAS;YAgBT,cAAc;IAUtB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CAkFlF"}
|
|
@@ -35,7 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.DuckDuckGoSearcher = void 0;
|
|
37
37
|
const cheerio = __importStar(require("cheerio"));
|
|
38
|
+
const types_1 = require("./types");
|
|
38
39
|
const rateLimiter_1 = require("./rateLimiter");
|
|
40
|
+
const cookieJar_1 = require("./cookieJar");
|
|
39
41
|
class DuckDuckGoSearcher {
|
|
40
42
|
constructor() {
|
|
41
43
|
this.rateLimiter = new rateLimiter_1.RateLimiter({ requestsPerMinute: 30 });
|
|
@@ -54,112 +56,199 @@ class DuckDuckGoSearcher {
|
|
|
54
56
|
}
|
|
55
57
|
return output.join('\n');
|
|
56
58
|
}
|
|
59
|
+
extractNextPageParams($) {
|
|
60
|
+
let nextForm = null;
|
|
61
|
+
$('.nav-link').each((_, el) => {
|
|
62
|
+
const form = $(el).find('form');
|
|
63
|
+
if (form.find('input[type="submit"][value="Next"]').length) {
|
|
64
|
+
nextForm = form;
|
|
65
|
+
return false; // break
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
if (!nextForm)
|
|
69
|
+
return null;
|
|
70
|
+
const get = (name) => nextForm.find(`input[name="${name}"]`).val() ?? '';
|
|
71
|
+
const vqd = get('vqd');
|
|
72
|
+
if (!vqd)
|
|
73
|
+
return null;
|
|
74
|
+
const params = {
|
|
75
|
+
s: get('s'),
|
|
76
|
+
nextParams: get('nextParams'),
|
|
77
|
+
v: get('v'),
|
|
78
|
+
o: get('o'),
|
|
79
|
+
dc: get('dc'),
|
|
80
|
+
api: get('api'),
|
|
81
|
+
vqd,
|
|
82
|
+
};
|
|
83
|
+
const kl = get('kl');
|
|
84
|
+
if (kl)
|
|
85
|
+
params['kl'] = kl;
|
|
86
|
+
return params;
|
|
87
|
+
}
|
|
88
|
+
isBotDetected(html, $) {
|
|
89
|
+
// 1. HTML too short — redirect or minimal block page
|
|
90
|
+
if (html.length < 1500)
|
|
91
|
+
return true;
|
|
92
|
+
// 2. Main results container missing
|
|
93
|
+
if ($('.serp__results').length === 0)
|
|
94
|
+
return true;
|
|
95
|
+
// 3. Page title doesn't match valid DDG pattern
|
|
96
|
+
const title = $('title').text();
|
|
97
|
+
if (title && !/at DuckDuckGo$/i.test(title))
|
|
98
|
+
return true;
|
|
99
|
+
// 4. Challenge/CAPTCHA keywords in body text
|
|
100
|
+
if (/(captcha|robot check|verify you are human|unusual traffic|access denied)/i.test(html))
|
|
101
|
+
return true;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
parseResults($, maxResults) {
|
|
105
|
+
const results = [];
|
|
106
|
+
$('.zci-wrapper').each((_index, element) => {
|
|
107
|
+
const $element = $(element);
|
|
108
|
+
const titleElement = $element.find('.zci__heading');
|
|
109
|
+
if (!titleElement.length)
|
|
110
|
+
return;
|
|
111
|
+
const title = titleElement.text().trim();
|
|
112
|
+
const snippetElement = $element.find('.zci__result');
|
|
113
|
+
if (!snippetElement.length)
|
|
114
|
+
return;
|
|
115
|
+
const snippet = snippetElement.text().trim();
|
|
116
|
+
results.push({
|
|
117
|
+
title,
|
|
118
|
+
link: '',
|
|
119
|
+
snippet: `[可信度: 最高,zero click results are present] ${snippet}`,
|
|
120
|
+
position: results.length + 1
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
$('.result').each((_index, element) => {
|
|
124
|
+
if (results.length >= maxResults)
|
|
125
|
+
return false;
|
|
126
|
+
const $element = $(element);
|
|
127
|
+
const titleElement = $element.find('.result__title');
|
|
128
|
+
if (!titleElement.length)
|
|
129
|
+
return;
|
|
130
|
+
const linkElement = titleElement.find('a');
|
|
131
|
+
if (!linkElement.length)
|
|
132
|
+
return;
|
|
133
|
+
const title = linkElement.text().trim();
|
|
134
|
+
let link = linkElement.attr('href') || '';
|
|
135
|
+
// Skip ad results
|
|
136
|
+
if (link.includes('y.js'))
|
|
137
|
+
return;
|
|
138
|
+
// Clean up DuckDuckGo redirect URLs
|
|
139
|
+
if (link.startsWith('//duckduckgo.com/l/?uddg=')) {
|
|
140
|
+
const decoded = decodeURIComponent(link.split('uddg=')[1]?.split('&')[0] || '');
|
|
141
|
+
link = decoded;
|
|
142
|
+
}
|
|
143
|
+
const snippetElement = $element.find('.result__snippet');
|
|
144
|
+
const snippet = snippetElement.text().trim() || '';
|
|
145
|
+
results.push({
|
|
146
|
+
title,
|
|
147
|
+
link,
|
|
148
|
+
snippet,
|
|
149
|
+
position: results.length + 1
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
return results;
|
|
153
|
+
}
|
|
154
|
+
async fetchPage(body, signal) {
|
|
155
|
+
const cookieHeader = cookieJar_1.cookieJar.getCookieHeader(DuckDuckGoSearcher.BASE_URL);
|
|
156
|
+
const response = await fetch(DuckDuckGoSearcher.BASE_URL, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
...DuckDuckGoSearcher.HEADERS,
|
|
160
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
161
|
+
...(cookieHeader ? { Cookie: cookieHeader } : {}),
|
|
162
|
+
},
|
|
163
|
+
body,
|
|
164
|
+
signal,
|
|
165
|
+
});
|
|
166
|
+
cookieJar_1.cookieJar.setCookies(response.headers, DuckDuckGoSearcher.BASE_URL);
|
|
167
|
+
return response;
|
|
168
|
+
}
|
|
169
|
+
async fireReportPing() {
|
|
170
|
+
try {
|
|
171
|
+
const r = await fetch(DuckDuckGoSearcher.BASE_URL_REPORT, {
|
|
172
|
+
method: 'GET',
|
|
173
|
+
headers: { ...DuckDuckGoSearcher.HEADERS_REPORT },
|
|
174
|
+
});
|
|
175
|
+
await r.blob();
|
|
176
|
+
}
|
|
177
|
+
catch { /* ignore */ }
|
|
178
|
+
}
|
|
57
179
|
async search(query, options = {}) {
|
|
58
180
|
const { maxResults = 10 } = options;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
181
|
+
const MAX_ATTEMPTS = 3;
|
|
182
|
+
const RETRY_DELAYS = [1000, 2000]; // ms between attempts
|
|
183
|
+
await this.rateLimiter.acquire();
|
|
184
|
+
// --- Page 1 with bot-detection retry ---
|
|
185
|
+
let page1$;
|
|
186
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
187
|
+
if (attempt > 0) {
|
|
188
|
+
await new Promise(r => setTimeout(r, RETRY_DELAYS[attempt - 1]));
|
|
189
|
+
}
|
|
62
190
|
const controller = new AbortController();
|
|
63
191
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
64
|
-
const
|
|
65
|
-
q: query,
|
|
66
|
-
b: ''
|
|
67
|
-
});
|
|
68
|
-
const response = await fetch(DuckDuckGoSearcher.BASE_URL, {
|
|
69
|
-
method: 'POST',
|
|
70
|
-
headers: {
|
|
71
|
-
...DuckDuckGoSearcher.HEADERS,
|
|
72
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
73
|
-
},
|
|
74
|
-
body: formData,
|
|
75
|
-
signal: controller.signal
|
|
76
|
-
});
|
|
192
|
+
const response = await this.fetchPage(new URLSearchParams({ q: query, b: '' }), controller.signal);
|
|
77
193
|
clearTimeout(timeoutId);
|
|
78
194
|
if (!response.ok) {
|
|
79
|
-
throw new
|
|
195
|
+
throw new types_1.SearchError(`HTTP ${response.status}: ${response.statusText}`, 'HTTP_ERROR');
|
|
80
196
|
}
|
|
81
197
|
const html = await response.text();
|
|
198
|
+
const $ = cheerio.load(html);
|
|
199
|
+
const botDetected = this.isBotDetected(html, $);
|
|
200
|
+
// Always complete report ping before retrying or throwing
|
|
201
|
+
await this.fireReportPing();
|
|
202
|
+
if (botDetected) {
|
|
203
|
+
if (attempt < MAX_ATTEMPTS - 1)
|
|
204
|
+
continue;
|
|
205
|
+
throw new types_1.SearchError('DuckDuckGo bot detection triggered after 3 attempts', 'BOT_DETECTED');
|
|
206
|
+
}
|
|
207
|
+
page1$ = $;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
// --- Paginate until maxResults satisfied ---
|
|
211
|
+
const allResults = [];
|
|
212
|
+
let $ = page1$;
|
|
213
|
+
// vqd is embedded in page 1's "Next" nav-link form — extract it directly.
|
|
214
|
+
// No extra HTTP request needed.
|
|
215
|
+
let nextParams = this.extractNextPageParams($);
|
|
216
|
+
const page1Results = this.parseResults($, maxResults);
|
|
217
|
+
for (const r of page1Results) {
|
|
218
|
+
r.position = allResults.length + 1;
|
|
219
|
+
allResults.push(r);
|
|
220
|
+
}
|
|
221
|
+
while (allResults.length < maxResults && nextParams) {
|
|
82
222
|
try {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
223
|
+
const body = new URLSearchParams({ q: query, ...nextParams });
|
|
224
|
+
const response = await this.fetchPage(body, new AbortController().signal);
|
|
225
|
+
if (!response.ok)
|
|
226
|
+
break;
|
|
227
|
+
const html = await response.text();
|
|
228
|
+
$ = cheerio.load(html);
|
|
229
|
+
if (this.isBotDetected(html, $))
|
|
230
|
+
break;
|
|
231
|
+
const pageResults = this.parseResults($, maxResults - allResults.length);
|
|
232
|
+
for (const r of pageResults) {
|
|
233
|
+
r.position = allResults.length + 1;
|
|
234
|
+
allResults.push(r);
|
|
91
235
|
}
|
|
92
|
-
|
|
236
|
+
nextParams = allResults.length < maxResults
|
|
237
|
+
? this.extractNextPageParams($)
|
|
238
|
+
: null;
|
|
93
239
|
}
|
|
94
|
-
catch
|
|
95
|
-
|
|
240
|
+
catch {
|
|
241
|
+
break;
|
|
96
242
|
}
|
|
97
|
-
const $ = cheerio.load(html);
|
|
98
|
-
const results = [];
|
|
99
|
-
$('.zci-wrapper').each((index, element) => {
|
|
100
|
-
const $element = $(element);
|
|
101
|
-
const titleElement = $element.find('.zci__heading');
|
|
102
|
-
if (!titleElement.length) {
|
|
103
|
-
return; // Continue to next iteration
|
|
104
|
-
}
|
|
105
|
-
const title = titleElement.text().trim();
|
|
106
|
-
const snippetElement = $element.find('.zci__result');
|
|
107
|
-
if (!snippetElement.length) {
|
|
108
|
-
return; // Continue to next iteration
|
|
109
|
-
}
|
|
110
|
-
const snippet = snippetElement.text().trim();
|
|
111
|
-
results.push({
|
|
112
|
-
title,
|
|
113
|
-
link: '',
|
|
114
|
-
snippet: `[可信度: 最高,zero click results are present] ${snippet}`,
|
|
115
|
-
position: results.length + 1
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
$('.result').each((index, element) => {
|
|
119
|
-
if (results.length >= maxResults) {
|
|
120
|
-
return false; // Break the loop
|
|
121
|
-
}
|
|
122
|
-
const $element = $(element);
|
|
123
|
-
const titleElement = $element.find('.result__title');
|
|
124
|
-
if (!titleElement.length) {
|
|
125
|
-
return; // Continue to next iteration
|
|
126
|
-
}
|
|
127
|
-
const linkElement = titleElement.find('a');
|
|
128
|
-
if (!linkElement.length) {
|
|
129
|
-
return; // Continue to next iteration
|
|
130
|
-
}
|
|
131
|
-
const title = linkElement.text().trim();
|
|
132
|
-
let link = linkElement.attr('href') || '';
|
|
133
|
-
// Skip ad results
|
|
134
|
-
if (link.includes('y.js')) {
|
|
135
|
-
return; // Continue to next iteration
|
|
136
|
-
}
|
|
137
|
-
// Clean up DuckDuckGo redirect URLs
|
|
138
|
-
if (link.startsWith('//duckduckgo.com/l/?uddg=')) {
|
|
139
|
-
const decoded = decodeURIComponent(link.split('uddg=')[1]?.split('&')[0] || '');
|
|
140
|
-
link = decoded;
|
|
141
|
-
}
|
|
142
|
-
const snippetElement = $element.find('.result__snippet');
|
|
143
|
-
const snippet = snippetElement.text().trim() || '';
|
|
144
|
-
results.push({
|
|
145
|
-
title,
|
|
146
|
-
link,
|
|
147
|
-
snippet,
|
|
148
|
-
position: results.length + 1
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
return results;
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
return [];
|
|
155
243
|
}
|
|
244
|
+
return allResults;
|
|
156
245
|
}
|
|
157
246
|
}
|
|
158
247
|
exports.DuckDuckGoSearcher = DuckDuckGoSearcher;
|
|
159
248
|
DuckDuckGoSearcher.BASE_URL = 'https://html.duckduckgo.com/html';
|
|
160
249
|
DuckDuckGoSearcher.BASE_URL_REPORT = 'https://duckduckgo.com/t/sl_h';
|
|
161
250
|
DuckDuckGoSearcher.HEADERS = {
|
|
162
|
-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
251
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36',
|
|
163
252
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
164
253
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
|
165
254
|
'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
@@ -169,22 +258,22 @@ DuckDuckGoSearcher.HEADERS = {
|
|
|
169
258
|
'Sec-Fetch-Mode': 'navigate',
|
|
170
259
|
'Referer': 'https://html.duckduckgo.com/',
|
|
171
260
|
'Origin': 'https://html.duckduckgo.com',
|
|
172
|
-
'Sec-Ch-Ua': '"Not
|
|
261
|
+
'Sec-Ch-Ua': '"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"',
|
|
173
262
|
'Sec-Ch-Ua-Mobile': '?0',
|
|
174
263
|
'Sec-Ch-Ua-Platform': '"Windows"',
|
|
175
264
|
'Sec-Fetch-Site': 'same-origin',
|
|
176
265
|
'Sec-Fetch-User': '?1',
|
|
177
|
-
'Cache-Control': '
|
|
266
|
+
'Cache-Control': 'max-age=0',
|
|
178
267
|
'Priority': 'u=0, i',
|
|
179
|
-
'Pragma': 'no-cache',
|
|
180
|
-
"Cookie": "kl=cn-zh"
|
|
181
|
-
};
|
|
182
|
-
DuckDuckGoSearcher.HEADERS_REPORT = {
|
|
183
|
-
...DuckDuckGoSearcher.HEADERS,
|
|
184
|
-
'Sec-Fetch-Site': 'same-site',
|
|
185
|
-
'Sec-Fetch-Mode': 'no-cors',
|
|
186
|
-
'Sec-Fetch-Dest': 'image',
|
|
187
|
-
'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
|
188
|
-
'Origin': undefined
|
|
189
268
|
};
|
|
269
|
+
DuckDuckGoSearcher.HEADERS_REPORT = (() => {
|
|
270
|
+
const { Origin: _removed, ...rest } = DuckDuckGoSearcher.HEADERS;
|
|
271
|
+
return {
|
|
272
|
+
...rest,
|
|
273
|
+
'Sec-Fetch-Site': 'same-site',
|
|
274
|
+
'Sec-Fetch-Mode': 'no-cors',
|
|
275
|
+
'Sec-Fetch-Dest': 'image',
|
|
276
|
+
'Accept': 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
|
|
277
|
+
};
|
|
278
|
+
})();
|
|
190
279
|
//# sourceMappingURL=duckduckgoSearcher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duckduckgoSearcher.js","sourceRoot":"","sources":["../src/duckduckgoSearcher.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iDAAmC;AAEnC,+CAA4C;
|
|
1
|
+
{"version":3,"file":"duckduckgoSearcher.js","sourceRoot":"","sources":["../src/duckduckgoSearcher.ts"],"names":[],"mappings":"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"}
|
package/build/index.js
CHANGED
|
@@ -32,13 +32,13 @@ class DuckDuckGoMCPServer {
|
|
|
32
32
|
tools: [
|
|
33
33
|
{
|
|
34
34
|
name: 'search',
|
|
35
|
-
description: 'Search DuckDuckGo and return formatted results',
|
|
35
|
+
description: 'Search DuckDuckGo and return formatted results. Supports advanced query syntax: site:github.com (limit to domain), "exact phrase", -exclude, filetype:pdf, intitle:keyword, OR / AND operators. Examples: "python async site:docs.python.org", "claude site:anthropic.com OR site:github.com"',
|
|
36
36
|
inputSchema: {
|
|
37
37
|
type: 'object',
|
|
38
38
|
properties: {
|
|
39
39
|
query: {
|
|
40
40
|
type: 'string',
|
|
41
|
-
description: '
|
|
41
|
+
description: 'Search query. Supports DuckDuckGo advanced syntax: site:domain.com (restrict to domain), "exact phrase", -word (exclude), OR/AND (combine), intitle:keyword, filetype:pdf. Example: "python tutorial site:docs.python.org OR site:realpython.com"',
|
|
42
42
|
},
|
|
43
43
|
max_results: {
|
|
44
44
|
type: 'integer',
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AAEA,wEAAmE;AACnE,wEAAiF;AACjF,iEAI4C;AAC5C,6DAA0D;AA6J3B,0FA7JtB,uCAAkB,OA6Ja;AA5JxC,2DAAwD;AA4JO,2FA5JtD,qCAAiB,OA4J+C;AAxJzE,SAAS,GAAG,CAAC,OAAe;IAC1B,4EAA4E;AAC9E,CAAC;AAGD,MAAM,mBAAmB;IAKvB;QACE,IAAI,CAAC,MAAM,GAAG,IAAI,iBAAM,CACtB;YACE,IAAI,EAAE,sBAAsB;YAC5B,OAAO,EAAE,OAAO;SACjB,EACD;YACE,YAAY,EAAE;gBACZ,KAAK,EAAE,EAAE;aACV;SACF,CACF,CAAC;QAEF,IAAI,CAAC,QAAQ,GAAG,IAAI,uCAAkB,EAAE,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,IAAI,qCAAiB,EAAE,CAAC;QAEvC,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,iCAAsB,EAAE,KAAK,IAAI,EAAE;YAC/D,OAAO;gBACL,KAAK,EAAE;oBACL;wBACE,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
CHANGED
|
@@ -10,4 +10,8 @@ export interface RateLimiterOptions {
|
|
|
10
10
|
export interface SearchOptions {
|
|
11
11
|
maxResults?: number;
|
|
12
12
|
}
|
|
13
|
+
export declare class SearchError extends Error {
|
|
14
|
+
readonly code: 'BOT_DETECTED' | 'HTTP_ERROR' | 'TIMEOUT' | 'UNKNOWN';
|
|
15
|
+
constructor(message: string, code: 'BOT_DETECTED' | 'HTTP_ERROR' | 'TIMEOUT' | 'UNKNOWN');
|
|
16
|
+
}
|
|
13
17
|
//# sourceMappingURL=types.d.ts.map
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,WAAY,SAAQ,KAAK;aAGlB,IAAI,EAAE,cAAc,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS;gBAD3E,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,cAAc,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS;CAK9E"}
|
package/build/types.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SearchError = void 0;
|
|
4
|
+
class SearchError extends Error {
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = 'SearchError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.SearchError = SearchError;
|
|
3
12
|
//# sourceMappingURL=types.js.map
|
package/build/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAeA,MAAa,WAAY,SAAQ,KAAK;IACpC,YACE,OAAe,EACC,IAA2D;QAE3E,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,SAAI,GAAJ,IAAI,CAAuD;QAG3E,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AARD,kCAQC"}
|
package/package.json
CHANGED
|
@@ -1,68 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "duckduckgo-websearch",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP Server for searching via DuckDuckGo (Node.js version)",
|
|
5
|
-
"main": "build/index.js",
|
|
6
|
-
"types": "build/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"duckduckgo-websearch": "build/index.js",
|
|
9
|
-
"ddg-websearch": "build/index.js"
|
|
10
|
-
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsc",
|
|
13
|
-
"dev": "
|
|
14
|
-
"start": "
|
|
15
|
-
"test:fetch": "
|
|
16
|
-
"test:fetch:debug": "
|
|
17
|
-
"clean": "
|
|
18
|
-
"prepare": "
|
|
19
|
-
"prepublishOnly": "
|
|
20
|
-
"publish:check": "
|
|
21
|
-
"version:patch": "npm version patch",
|
|
22
|
-
"version:minor": "npm version minor",
|
|
23
|
-
"version:major": "npm version major"
|
|
24
|
-
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
"mcp",
|
|
27
|
-
"model-context-protocol",
|
|
28
|
-
"duckduckgo",
|
|
29
|
-
"search",
|
|
30
|
-
"ai",
|
|
31
|
-
"llm"
|
|
32
|
-
],
|
|
33
|
-
"author": {
|
|
34
|
-
"name": "HeiSir2014",
|
|
35
|
-
"email": "heisir21@163.com"
|
|
36
|
-
},
|
|
37
|
-
"license": "MIT",
|
|
38
|
-
"dependencies": {
|
|
39
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
40
|
-
"cheerio": "^1.
|
|
41
|
-
},
|
|
42
|
-
"devDependencies": {
|
|
43
|
-
"@types/node": "^
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"homepage": "https://github.com/HeiSir2014/duckduckgo-mcp-server"
|
|
68
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "duckduckgo-websearch",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "MCP Server for searching via DuckDuckGo (Node.js version)",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"duckduckgo-websearch": "build/index.js",
|
|
9
|
+
"ddg-websearch": "build/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "bun src/index.ts",
|
|
14
|
+
"start": "bun build/index.js",
|
|
15
|
+
"test:fetch": "bun example/fetch-test.ts",
|
|
16
|
+
"test:fetch:debug": "bun --inspect-brk example/fetch-test.ts",
|
|
17
|
+
"clean": "rm -rf build",
|
|
18
|
+
"prepare": "bun run build",
|
|
19
|
+
"prepublishOnly": "bun run clean && bun run build",
|
|
20
|
+
"publish:check": "bun scripts/publish.js",
|
|
21
|
+
"version:patch": "npm version patch",
|
|
22
|
+
"version:minor": "npm version minor",
|
|
23
|
+
"version:major": "npm version major"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"duckduckgo",
|
|
29
|
+
"search",
|
|
30
|
+
"ai",
|
|
31
|
+
"llm"
|
|
32
|
+
],
|
|
33
|
+
"author": {
|
|
34
|
+
"name": "HeiSir2014",
|
|
35
|
+
"email": "heisir21@163.com"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
40
|
+
"cheerio": "^1.2.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.5.0",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18.0.0",
|
|
48
|
+
"bun": ">=1.0.0"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"build/",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"repository": {
|
|
59
|
+
"type": "git",
|
|
60
|
+
"url": "git+https://github.com/HeiSir2014/duckduckgo-mcp-server.git"
|
|
61
|
+
},
|
|
62
|
+
"bugs": {
|
|
63
|
+
"url": "https://github.com/HeiSir2014/duckduckgo-mcp-server/issues"
|
|
64
|
+
},
|
|
65
|
+
"homepage": "https://github.com/HeiSir2014/duckduckgo-mcp-server"
|
|
66
|
+
}
|