@johnnren/mcp-searxng-public-extended 0.1.6

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.
Files changed (52) hide show
  1. package/.env.example +30 -0
  2. package/.github/workflows/release.yml +46 -0
  3. package/README.md +82 -0
  4. package/README.zh-CN.md +84 -0
  5. package/dist/browser.d.ts +10 -0
  6. package/dist/browser.d.ts.map +1 -0
  7. package/dist/browser.js +46 -0
  8. package/dist/browser.js.map +1 -0
  9. package/dist/config.d.ts +19 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +47 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/i18n.d.ts +54 -0
  14. package/dist/i18n.d.ts.map +1 -0
  15. package/dist/i18n.js +33 -0
  16. package/dist/i18n.js.map +1 -0
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +111 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/parser.d.ts +6 -0
  22. package/dist/parser.d.ts.map +1 -0
  23. package/dist/parser.js +79 -0
  24. package/dist/parser.js.map +1 -0
  25. package/dist/search.d.ts +33 -0
  26. package/dist/search.d.ts.map +1 -0
  27. package/dist/search.js +213 -0
  28. package/dist/search.js.map +1 -0
  29. package/dist/throttle.d.ts +33 -0
  30. package/dist/throttle.d.ts.map +1 -0
  31. package/dist/throttle.js +81 -0
  32. package/dist/throttle.js.map +1 -0
  33. package/dist/types.d.ts +52 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +2 -0
  36. package/dist/types.js.map +1 -0
  37. package/dist/utils.d.ts +14 -0
  38. package/dist/utils.d.ts.map +1 -0
  39. package/dist/utils.js +49 -0
  40. package/dist/utils.js.map +1 -0
  41. package/package.json +46 -0
  42. package/src/browser.ts +55 -0
  43. package/src/config.ts +74 -0
  44. package/src/i18n.ts +52 -0
  45. package/src/index.ts +165 -0
  46. package/src/parser.ts +99 -0
  47. package/src/search.ts +310 -0
  48. package/src/throttle.ts +103 -0
  49. package/src/types.ts +56 -0
  50. package/src/utils.ts +63 -0
  51. package/test.cjs +179 -0
  52. package/tsconfig.json +21 -0
package/.env.example ADDED
@@ -0,0 +1,30 @@
1
+ # SearXNG base URLs (semicolon-separated)
2
+ SEARXNG_BASE_URL=https://search.ononoki.org;https://searx.tiekoetter.com;https://opnxng.com
3
+
4
+ # Default language for searches
5
+ DEFAULT_LANGUAGE=en
6
+
7
+ # Number of servers to randomly select per search (number or 'all')
8
+ SEARXNG_BATCH_SIZE=1
9
+
10
+ # Merge results from top N fastest servers
11
+ SEARXNG_MIN_SERVERS=1
12
+
13
+ # Default search engines (comma-separated)
14
+ # Common engines: google, bing, duckduckgo, brave, qwant, startpage, wikipedia, github, stackoverflow, reddit, youtube, arxiv
15
+ # Full list: https://docs.searxng.org/user/configured_engines.html
16
+ SEARXNG_DEFAULT_ENGINES=
17
+
18
+ # Default number of pages to fetch
19
+ SEARXNG_DEFAULT_PAGES=1
20
+
21
+ # Default safe search level: 0=off, 1=moderate, 2=strict
22
+ # Leave empty to use server default
23
+ SEARXNG_DEFAULT_SAFESARCH=
24
+
25
+ # Fields to include in results (comma-separated)
26
+ # Available: url, title, summary, engine, sourceServer
27
+ # Default: all fields
28
+ SEARXNG_RESULT_FIELDS=url,title,summary,engine,sourceServer
29
+
30
+ SEARXNG_MIN_INTERVAL=450
@@ -0,0 +1,46 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ id-token: write
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup pnpm
19
+ uses: pnpm/action-setup@v4
20
+ with:
21
+ version: 9
22
+
23
+ - name: Setup Node.js
24
+ uses: actions/setup-node@v4
25
+ with:
26
+ node-version: '20'
27
+ registry-url: 'https://registry.npmjs.org'
28
+ cache: 'pnpm'
29
+
30
+ - name: Install dependencies
31
+ run: pnpm install --frozen-lockfile
32
+
33
+ - name: Build
34
+ run: pnpm build
35
+
36
+ - name: Publish to npm
37
+ run: |
38
+ echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > ~/.npmrc
39
+ pnpm publish --provenance --access public --no-git-checks
40
+ env:
41
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
42
+
43
+ - name: Create GitHub Release
44
+ uses: softprops/action-gh-release@v2
45
+ with:
46
+ generate_release_notes: true
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @johnnren/mcp-searxng-public-extended
2
+
3
+ Free web search for your AI using public SearXNG instances.
4
+
5
+ > **Acknowledgment**: Inspired by [pwilkin/mcp-searxng-public](https://github.com/pwilkin/mcp-searxng-public), extended with parallel multi-server requests, global throttling, configurable parameters, and bilingual schema support (zh/en).
6
+
7
+ ## Features
8
+
9
+ - **HTML Parsing**: Most public SearXNG instances disable JSON API, this server parses HTML responses directly
10
+ - **Rich Parameters**: categories, engines, safesearch, time range, language, pagination, return fields
11
+ - **Parallel Racing & Deduplication**: Randomly select `BATCH_SIZE` servers, query in parallel, merge results from top `MIN_SERVERS` fastest
12
+ - **Auto Throttling**: Per-server request queue with configurable minimum interval, preventing rate limiting from public instances
13
+
14
+ ## Usage with MCP Clients
15
+
16
+ Add to your MCP client configuration:
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "searxng": {
22
+ "command": "npx",
23
+ "args": ["-y", "@johnnren/mcp-searxng-public-extended"],
24
+ "env": {
25
+ "SEARXNG_BASE_URL": "https://opnxng.com;https://priv.au;https://searx.perennialte.ch;https://searx.rhscz.eu",
26
+ "SEARXNG_VISIBLE_PARAMETERS": "query,categories,time_range,language,startPage",
27
+ "SEARXNG_DEFAULT_ENGINES": "google",
28
+ "SEARXNG_DEFAULT_PAGES": "1",
29
+ "SEARXNG_DEFAULT_SAFESARCH": "0"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ > Hiding `pages` and `engines` with `SEARXNG_VISIBLE_PARAMETERS` improves stability (not all servers support all engines) and reduces response size (one page is usually enough).
37
+
38
+ ## Environment Variables
39
+
40
+ | Variable | Description | Default |
41
+ | ---------------------------- | -------------------------------------------------------------------- | -------------- |
42
+ | `SEARXNG_BASE_URL` | SearXNG server URLs (semicolon-separated) **Required** | - |
43
+ | `SEARXNG_SCHEMA_LANG` | Schema language: `zh` (Chinese) or `en` (English) | `zh` |
44
+ | `SEARXNG_DEFAULT_LANGUAGE` | Default language code | - |
45
+ | `SEARXNG_BATCH_SIZE` | Servers to query per search (number or `all`) | `1` |
46
+ | `SEARXNG_MIN_SERVERS` | Merge results from top N fastest servers | `1` |
47
+ | `SEARXNG_DEFAULT_ENGINES` | Default engines (comma-separated) | Server default |
48
+ | `SEARXNG_DEFAULT_PAGES` | Default pages to fetch | `1` |
49
+ | `SEARXNG_DEFAULT_SAFESARCH` | Safe search level (0=off, 1=moderate, 2=strict) | Server default |
50
+ | `SEARXNG_MIN_INTERVAL` | Min interval between requests to same server (ms) | `450` |
51
+ | `SEARXNG_RESULT_FIELDS` | Fields included in result: url, title, summary, engine, sourceServer | All fields |
52
+ | `SEARXNG_VISIBLE_PARAMETERS` | Parameters visible to LLM | `all` |
53
+
54
+ ## Tool: `search`
55
+
56
+ Web search via SearXNG.
57
+
58
+ **Parameters:**
59
+
60
+ | Parameter | Type | Required | Description |
61
+ | ------------ | ------ | -------- | ------------------------------------------------------------------------ |
62
+ | `query` | string | Yes | Search query |
63
+ | `categories` | string | No | Categories: `general`, `images`, `news`, `videos`, `science`, `it`, etc. |
64
+ | `engines` | string | No | Engines: `google`, `bing`, `duckduckgo`, `github`, `stackoverflow`, etc. |
65
+ | `safesearch` | number | No | Level: 0=off, 1=moderate, 2=strict |
66
+ | `time_range` | string | No | Filter: `day`, `week`, `month`, `year` |
67
+ | `language` | string | No | Language code (e.g., `en`, `zh`) |
68
+ | `pages` | number | No | Pages to fetch |
69
+ | `startPage` | number | No | Starting page number |
70
+
71
+ **Returns:** Array of `{ url, title, summary, engine, sourceServer }`
72
+
73
+ ## Publishing
74
+
75
+ ```bash
76
+ npm version patch # or minor/major
77
+ git push --follow-tags
78
+ ```
79
+
80
+ ## License
81
+
82
+ MIT
@@ -0,0 +1,84 @@
1
+ # @johnnren/mcp-searxng-public-extended
2
+
3
+ 使用公共 SearXNG 实例为你的 AI 提供免费网络搜索能力。
4
+
5
+ > **致谢**:参考 [pwilkin/mcp-searxng-public](https://github.com/pwilkin/mcp-searxng-public),扩展了并行多服务器请求、全局节流、可配置参数、双语 Schema 支持(中/英)。
6
+
7
+ ## 特性
8
+
9
+ - **HTML 解析**:大多数公共 SearXNG 实例禁用了 JSON API,本服务器直接解析 HTML 响应
10
+ - **丰富参数**:支持分类、引擎、安全搜索、时间范围、语言、分页、返回格式
11
+ - **并行与去重**:随机选择 `BATCH_SIZE` 个服务器,并行查询,合并前 `MIN_SERVERS` 个最快响应的结果
12
+ - **自动节流**:全局请求队列,按服务器自动排队,可配置最小请求间隔,避免触发公共实例的频率限制
13
+
14
+ ## MCP 客户端配置
15
+
16
+ ```json
17
+ {
18
+ "mcpServers": {
19
+ "searxng": {
20
+ "command": "npx",
21
+ "args": ["-y", "@johnnren/mcp-searxng-public-extended"],
22
+ "env": {
23
+ "SEARXNG_BASE_URL": "https://opnxng.com;https://priv.au;https://searx.perennialte.ch;https://searx.rhscz.eu",
24
+ "SEARXNG_VISIBLE_PARAMETERS": "query,categories,time_range,language,startPage",
25
+ "SEARXNG_DEFAULT_ENGINES": "google",
26
+ "SEARXNG_DEFAULT_PAGES": "1",
27
+ "SEARXNG_DEFAULT_SAFESARCH": "0"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### 支持的环境变量选项
35
+
36
+ | 环境变量 | 说明 | 默认值 |
37
+ | ---------------------------- | -------------------------------------------- | --------------------------------------- |
38
+ | `SEARXNG_BASE_URL` | SearXNG 服务器地址(分号分隔)**必需** | - |
39
+ | `SEARXNG_SCHEMA_LANG` | Schema 语言:`zh`(中文)或 `en`(英文) | `zh` |
40
+ | `SEARXNG_DEFAULT_LANGUAGE` | 默认语言代码 | - |
41
+ | `SEARXNG_BATCH_SIZE` | 每次搜索随机选择的服务器数量(数字或 `all`) | `1` |
42
+ | `SEARXNG_MIN_SERVERS` | 合并前 N 个最快服务器的结果 | `1` |
43
+ | `SEARXNG_DEFAULT_ENGINES` | 默认搜索引擎(逗号分隔) | 服务器默认 |
44
+ | `SEARXNG_DEFAULT_PAGES` | 默认获取页数 | `1` |
45
+ | `SEARXNG_DEFAULT_SAFESARCH` | 默认安全搜索级别(0=关闭, 1=中等, 2=严格) | 服务器默认 |
46
+ | `SEARXNG_MIN_INTERVAL` | 同一服务器两次请求的最小间隔(毫秒) | `450` |
47
+ | `SEARXNG_RESULT_FIELDS` | 返回结果包含的字段 | `url,title,summary,engine,sourceServer` |
48
+ | `SEARXNG_VISIBLE_PARAMETERS` | 对 LLM 可见的参数 | `all` |
49
+
50
+ > `SEARXNG_VISIBLE_PARAMETERS` 中省略 `pages` 和 `engines` 可提高稳定性(部分服务器不支持所有引擎)并减少响应量(一页内容通常足够)。
51
+
52
+ ## 工具说明
53
+
54
+ 本服务器提供 `search` 工具。
55
+
56
+ ### `search`
57
+
58
+ 通过 SearXNG 进行网页搜索。
59
+
60
+ **参数:**
61
+
62
+ | 参数 | 类型 | 必需 | 说明 |
63
+ | ------------ | ------ | ---- | ------------------------------------------------------------------ |
64
+ | `query` | string | 是 | 搜索查询 |
65
+ | `categories` | string | 否 | 分类:`general`, `images`, `news`, `videos`, `science`, `it` 等 |
66
+ | `engines` | string | 否 | 引擎:`google`, `bing`, `duckduckgo`, `github`, `stackoverflow` 等 |
67
+ | `safesearch` | number | 否 | 级别:0=关闭, 1=中等, 2=严格 |
68
+ | `time_range` | string | 否 | 过滤:`day`, `week`, `month`, `year` |
69
+ | `language` | string | 否 | 语言代码(如 `en`, `zh`) |
70
+ | `pages` | number | 否 | 获取页数 |
71
+ | `startPage` | number | 否 | 起始页码 |
72
+
73
+ **返回:** `{ url, title, summary, engine, sourceServer }` 数组
74
+
75
+ ## 发布
76
+
77
+ ```bash
78
+ npm version patch # or minor/major
79
+ git push --follow-tags
80
+ ```
81
+
82
+ ## 许可证
83
+
84
+ MIT
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 获取随机 User-Agent
3
+ */
4
+ export declare function getRandomUserAgent(): string;
5
+ /**
6
+ * 生成浏览器请求头
7
+ * Sec-Fetch-Mode: navigate 是关键,防止被重定向
8
+ */
9
+ export declare function getBrowserHeaders(baseUrl: string, userAgent: string): Record<string, string>;
10
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAkBA;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsBxB"}
@@ -0,0 +1,46 @@
1
+ import { randomInt } from 'crypto';
2
+ // ============ 浏览器指纹 ============
3
+ // 常见浏览器 User-Agent 列表(更新到最新版本)
4
+ const USER_AGENTS = [
5
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
6
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
7
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
8
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15',
9
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
10
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0',
11
+ ];
12
+ // Chrome 版本列表(用于 Sec-Ch-Ua)
13
+ const CHROME_VERSIONS = ['122', '121', '120', '119'];
14
+ const CHROME_PLATFORMS = ['Windows', 'macOS', 'Linux'];
15
+ /**
16
+ * 获取随机 User-Agent
17
+ */
18
+ export function getRandomUserAgent() {
19
+ return USER_AGENTS[randomInt(0, USER_AGENTS.length)];
20
+ }
21
+ /**
22
+ * 生成浏览器请求头
23
+ * Sec-Fetch-Mode: navigate 是关键,防止被重定向
24
+ */
25
+ export function getBrowserHeaders(baseUrl, userAgent) {
26
+ const chromeVersion = CHROME_VERSIONS[randomInt(0, CHROME_VERSIONS.length)];
27
+ const platform = CHROME_PLATFORMS[randomInt(0, CHROME_PLATFORMS.length)];
28
+ return {
29
+ 'User-Agent': userAgent,
30
+ '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',
31
+ 'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
32
+ 'Accept-Encoding': 'gzip, deflate, br',
33
+ 'Cache-Control': 'max-age=0',
34
+ 'Sec-Ch-Ua': `"Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}", "Not(A:Brand";v="24"`,
35
+ 'Sec-Ch-Ua-Mobile': '?0',
36
+ 'Sec-Ch-Ua-Platform': `"${platform}"`,
37
+ 'Sec-Fetch-Dest': 'document',
38
+ 'Sec-Fetch-Mode': 'navigate',
39
+ 'Sec-Fetch-Site': 'same-origin',
40
+ 'Sec-Fetch-User': '?1',
41
+ 'Upgrade-Insecure-Requests': '1',
42
+ 'Referer': baseUrl + '/',
43
+ 'dnt': '1',
44
+ };
45
+ }
46
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAElC,kCAAkC;AAElC,+BAA+B;AAC/B,MAAM,WAAW,GAAG;IAClB,iHAAiH;IACjH,kFAAkF;IAClF,uHAAuH;IACvH,uHAAuH;IACvH,uGAAuG;IACvG,+HAA+H;CAChI,CAAA;AAED,4BAA4B;AAC5B,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;AACpD,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AAEtD;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,SAAiB;IAEjB,MAAM,aAAa,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC,CAAA;IAC3E,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAA;IAExE,OAAO;QACL,YAAY,EAAE,SAAS;QACvB,QAAQ,EACN,yIAAyI;QAC3I,iBAAiB,EAAE,qCAAqC;QACxD,iBAAiB,EAAE,mBAAmB;QACtC,eAAe,EAAE,WAAW;QAC5B,WAAW,EAAE,iBAAiB,aAAa,yBAAyB,aAAa,yBAAyB;QAC1G,kBAAkB,EAAE,IAAI;QACxB,oBAAoB,EAAE,IAAI,QAAQ,GAAG;QACrC,gBAAgB,EAAE,UAAU;QAC5B,gBAAgB,EAAE,UAAU;QAC5B,gBAAgB,EAAE,aAAa;QAC/B,gBAAgB,EAAE,IAAI;QACtB,2BAA2B,EAAE,GAAG;QAChC,SAAS,EAAE,OAAO,GAAG,GAAG;QACxB,KAAK,EAAE,GAAG;KACX,CAAA;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ export declare const BASE_URLS: string[];
3
+ export declare const DEFAULT_LANGUAGE: string;
4
+ export declare const BATCH_SIZE: string;
5
+ export declare const MIN_SERVERS: number;
6
+ export declare const DEFAULT_ENGINES: string;
7
+ export declare const DEFAULT_PAGES: number;
8
+ export declare const DEFAULT_SAFESARCH: number | undefined;
9
+ export declare const DEFAULT_CATEGORIES: string;
10
+ export declare const DEFAULT_TIME_RANGE: string;
11
+ export declare const MIN_INTERVAL: number;
12
+ export declare const RESULT_FIELDS: string[];
13
+ /** 检查参数是否对 LLM 可见 */
14
+ export declare const isVisible: (name: string) => boolean;
15
+ /** 条件展开:仅在参数可见时添加到 schema */
16
+ export declare const opt: <T extends z.ZodTypeAny>(name: string, schema: T) => {
17
+ [x: string]: T;
18
+ };
19
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,eAAO,MAAM,SAAS,EAAE,MAAM,EAGZ,CAAA;AAGlB,eAAO,MAAM,gBAAgB,QAA+C,CAAA;AAG5E,eAAO,MAAM,UAAU,QAAwC,CAAA;AAG/D,eAAO,MAAM,WAAW,QAAuD,CAAA;AAG/E,eAAO,MAAM,eAAe,QAA4C,CAAA;AAGxE,eAAO,MAAM,aAAa,QAGzB,CAAA;AAGD,eAAO,MAAM,iBAAiB,oBAEjB,CAAA;AAGb,eAAO,MAAM,kBAAkB,QAA+C,CAAA;AAG9E,eAAO,MAAM,kBAAkB,QAA+C,CAAA;AAG9E,eAAO,MAAM,YAAY,QAGxB,CAAA;AAKD,eAAO,MAAM,aAAa,UAOvB,CAAA;AAYH,qBAAqB;AACrB,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,KAAG,OACoB,CAAA;AAE7D,6BAA6B;AAC7B,eAAO,MAAM,GAAG,GAAI,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,MAAM,MAAM,EAAE,QAAQ,CAAC;;CACxB,CAAA"}
package/dist/config.js ADDED
@@ -0,0 +1,47 @@
1
+ // ============ 环境变量配置 ============
2
+ // SearXNG 服务器列表(分号分隔)
3
+ export const BASE_URLS = (process.env.SEARXNG_BASE_URL || '')
4
+ .split(';')
5
+ .map((url) => url.trim())
6
+ .filter(Boolean);
7
+ // 默认语言
8
+ export const DEFAULT_LANGUAGE = process.env.SEARXNG_DEFAULT_LANGUAGE || 'en';
9
+ // 每次搜索随机抽取几个服务器(number 或 'all')
10
+ export const BATCH_SIZE = process.env.SEARXNG_BATCH_SIZE || '1';
11
+ // 取前几个最快返回的服务器的结果聚合
12
+ export const MIN_SERVERS = parseInt(process.env.SEARXNG_MIN_SERVERS || '1', 10);
13
+ // 默认搜索引擎
14
+ export const DEFAULT_ENGINES = process.env.SEARXNG_DEFAULT_ENGINES || '';
15
+ // 默认获取页数
16
+ export const DEFAULT_PAGES = parseInt(process.env.SEARXNG_DEFAULT_PAGES || '1', 10);
17
+ // 默认安全搜索级别
18
+ export const DEFAULT_SAFESARCH = process.env.SEARXNG_DEFAULT_SAFESARCH
19
+ ? parseInt(process.env.SEARXNG_DEFAULT_SAFESARCH, 10)
20
+ : undefined;
21
+ // 默认搜索类别
22
+ export const DEFAULT_CATEGORIES = process.env.SEARXNG_DEFAULT_CATEGORIES || '';
23
+ // 默认时间范围
24
+ export const DEFAULT_TIME_RANGE = process.env.SEARXNG_DEFAULT_TIME_RANGE || '';
25
+ // 同一服务器两次请求的最小间隔(毫秒)
26
+ export const MIN_INTERVAL = parseInt(process.env.SEARXNG_MIN_INTERVAL || '450', 10);
27
+ // 结果中包含的字段(逗号分隔,如 "url,title,summary")
28
+ // 可选值: url, title, summary, engine, sourceServer
29
+ // 默认包含所有字段
30
+ export const RESULT_FIELDS = (process.env.SEARXNG_RESULT_FIELDS || 'url,title,summary,engine,sourceServer')
31
+ .split(',')
32
+ .map((f) => f.trim().toLowerCase())
33
+ .filter((f) => ['url', 'title', 'summary', 'engine', 'sourceserver'].includes(f));
34
+ // ============ 参数可见性 ============
35
+ // 对 LLM 可见的参数(逗号分隔)
36
+ // 默认: 全部可见(留空或设为 "all")
37
+ const VISIBLE_SET = (() => {
38
+ const envValue = process.env.SEARXNG_VISIBLE_PARAMETERS || '';
39
+ if (!envValue || envValue.toLowerCase() === 'all')
40
+ return null;
41
+ return new Set(envValue.split(',').map((p) => p.trim().toLowerCase()));
42
+ })();
43
+ /** 检查参数是否对 LLM 可见 */
44
+ export const isVisible = (name) => VISIBLE_SET === null || VISIBLE_SET.has(name.toLowerCase());
45
+ /** 条件展开:仅在参数可见时添加到 schema */
46
+ export const opt = (name, schema) => isVisible(name) ? { [name]: schema } : {};
47
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 国际化模块:支持中英文 schema 描述
3
+ */
4
+ /** 当前语言(从环境变量读取,默认中文) */
5
+ export declare const CURRENT_LANG: Language;
6
+ export type Language = 'zh' | 'en';
7
+ /** Schema 描述文本 */
8
+ export declare const descriptions: {
9
+ readonly zh: {
10
+ readonly toolDescription: "使用 SearXNG 进行网络搜索,返回最快有效结果。";
11
+ readonly query: "搜索查询";
12
+ readonly categories: (defaultVal: string) => string;
13
+ readonly engines: (defaultVal: string) => string;
14
+ readonly safesearch: (defaultVal: number | undefined) => string;
15
+ readonly timeRange: (defaultVal: string) => string;
16
+ readonly language: (defaultVal: string) => string;
17
+ readonly pages: (defaultVal: number) => string;
18
+ readonly startpage: "起始页码。默认: 1";
19
+ };
20
+ readonly en: {
21
+ readonly toolDescription: "Web search using SearXNG, returns the fastest valid results.";
22
+ readonly query: "Search query";
23
+ readonly categories: (defaultVal: string) => string;
24
+ readonly engines: (defaultVal: string) => string;
25
+ readonly safesearch: (defaultVal: number | undefined) => string;
26
+ readonly timeRange: (defaultVal: string) => string;
27
+ readonly language: (defaultVal: string) => string;
28
+ readonly pages: (defaultVal: number) => string;
29
+ readonly startpage: "Starting page number. Default: 1";
30
+ };
31
+ };
32
+ /** 获取当前语言的描述 */
33
+ export declare const t: {
34
+ readonly toolDescription: "使用 SearXNG 进行网络搜索,返回最快有效结果。";
35
+ readonly query: "搜索查询";
36
+ readonly categories: (defaultVal: string) => string;
37
+ readonly engines: (defaultVal: string) => string;
38
+ readonly safesearch: (defaultVal: number | undefined) => string;
39
+ readonly timeRange: (defaultVal: string) => string;
40
+ readonly language: (defaultVal: string) => string;
41
+ readonly pages: (defaultVal: number) => string;
42
+ readonly startpage: "起始页码。默认: 1";
43
+ } | {
44
+ readonly toolDescription: "Web search using SearXNG, returns the fastest valid results.";
45
+ readonly query: "Search query";
46
+ readonly categories: (defaultVal: string) => string;
47
+ readonly engines: (defaultVal: string) => string;
48
+ readonly safesearch: (defaultVal: number | undefined) => string;
49
+ readonly timeRange: (defaultVal: string) => string;
50
+ readonly language: (defaultVal: string) => string;
51
+ readonly pages: (defaultVal: number) => string;
52
+ readonly startpage: "Starting page number. Default: 1";
53
+ };
54
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yBAAyB;AACzB,eAAO,MAAM,YAAY,EAAE,QAEA,CAAA;AAE3B,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAA;AAElC,kBAAkB;AAClB,eAAO,MAAM,YAAY;;;;0CAII,MAAM;uCAET,MAAM;0CAEH,MAAM,GAAG,SAAS;yCAEnB,MAAM;wCAEP,MAAM;qCAET,MAAM;;;;;;0CAQD,MAAM;uCAET,MAAM;0CAEH,MAAM,GAAG,SAAS;yCAEnB,MAAM;wCAEP,MAAM;qCAET,MAAM;;;CAIpB,CAAA;AAEV,gBAAgB;AAChB,eAAO,MAAM,CAAC;;;sCAnCe,MAAM;mCAET,MAAM;sCAEH,MAAM,GAAG,SAAS;qCAEnB,MAAM;oCAEP,MAAM;iCAET,MAAM;;;;;sCAQD,MAAM;mCAET,MAAM;sCAEH,MAAM,GAAG,SAAS;qCAEnB,MAAM;oCAEP,MAAM;iCAET,MAAM;;CAOa,CAAA"}
package/dist/i18n.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 国际化模块:支持中英文 schema 描述
3
+ */
4
+ /** 当前语言(从环境变量读取,默认中文) */
5
+ export const CURRENT_LANG = (process.env.SEARXNG_SCHEMA_LANG || 'zh').toLowerCase();
6
+ /** Schema 描述文本 */
7
+ export const descriptions = {
8
+ zh: {
9
+ toolDescription: '使用 SearXNG 进行网络搜索,返回最快有效结果。',
10
+ query: '搜索查询',
11
+ categories: (defaultVal) => `搜索类别,用逗号分隔。支持:general, images, news, music, videos, science, it, social_media。默认: ${defaultVal || '不指定'}`,
12
+ engines: (defaultVal) => `搜索引擎,用逗号分隔。支持:google, bing, duckduckgo, brave, qwant, startpage, wikipedia, github, stackoverflow, reddit, youtube, arxiv。默认: ${defaultVal || '不指定'}`,
13
+ safesearch: (defaultVal) => `安全搜索: 0=关闭, 1=中等, 2=严格。默认: ${defaultVal ?? '不指定'}`,
14
+ timeRange: (defaultVal) => `过滤时间范围: day=一天内, week=一周内, month=一个月内, year=一年内。默认: ${defaultVal || '不指定'}`,
15
+ language: (defaultVal) => `语言代码 (en, zh, ja)。默认: ${defaultVal}`,
16
+ pages: (defaultVal) => `获取几页结果(建议每次获取 1 页)。默认: ${defaultVal}`,
17
+ startpage: '起始页码。默认: 1',
18
+ },
19
+ en: {
20
+ toolDescription: 'Web search using SearXNG, returns the fastest valid results.',
21
+ query: 'Search query',
22
+ categories: (defaultVal) => `Search categories, comma-separated. Supported: general, images, news, music, videos, science, it, social_media. Default: ${defaultVal || 'not specified'}`,
23
+ engines: (defaultVal) => `Search engines, comma-separated. Supported: google, bing, duckduckgo, brave, qwant, startpage, wikipedia, github, stackoverflow, reddit, youtube, arxiv. Default: ${defaultVal || 'not specified'}`,
24
+ safesearch: (defaultVal) => `Safe search: 0=off, 1=moderate, 2=strict. Default: ${defaultVal ?? 'not specified'}`,
25
+ timeRange: (defaultVal) => `Time range filter: day, week, month, year. Default: ${defaultVal || 'not specified'}`,
26
+ language: (defaultVal) => `Language code (en, zh, ja). Default: ${defaultVal}`,
27
+ pages: (defaultVal) => `Number of result pages to fetch (1 page recommended). Default: ${defaultVal}`,
28
+ startpage: 'Starting page number. Default: 1',
29
+ },
30
+ };
31
+ /** 获取当前语言的描述 */
32
+ export const t = descriptions[CURRENT_LANG];
33
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,yBAAyB;AACzB,MAAM,CAAC,MAAM,YAAY,GAAa,CACpC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CACxC,CAAC,WAAW,EAAc,CAAA;AAI3B,kBAAkB;AAClB,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,EAAE,EAAE;QACF,eAAe,EAAE,6BAA6B;QAC9C,KAAK,EAAE,MAAM;QACb,UAAU,EAAE,CAAC,UAAkB,EAAE,EAAE,CACjC,qFAAqF,UAAU,IAAI,KAAK,EAAE;QAC5G,OAAO,EAAE,CAAC,UAAkB,EAAE,EAAE,CAC9B,iIAAiI,UAAU,IAAI,KAAK,EAAE;QACxJ,UAAU,EAAE,CAAC,UAA8B,EAAE,EAAE,CAC7C,8BAA8B,UAAU,IAAI,KAAK,EAAE;QACrD,SAAS,EAAE,CAAC,UAAkB,EAAE,EAAE,CAChC,uDAAuD,UAAU,IAAI,KAAK,EAAE;QAC9E,QAAQ,EAAE,CAAC,UAAkB,EAAE,EAAE,CAC/B,yBAAyB,UAAU,EAAE;QACvC,KAAK,EAAE,CAAC,UAAkB,EAAE,EAAE,CAC5B,0BAA0B,UAAU,EAAE;QACxC,SAAS,EAAE,YAAY;KACxB;IACD,EAAE,EAAE;QACF,eAAe,EACb,8DAA8D;QAChE,KAAK,EAAE,cAAc;QACrB,UAAU,EAAE,CAAC,UAAkB,EAAE,EAAE,CACjC,4HAA4H,UAAU,IAAI,eAAe,EAAE;QAC7J,OAAO,EAAE,CAAC,UAAkB,EAAE,EAAE,CAC9B,qKAAqK,UAAU,IAAI,eAAe,EAAE;QACtM,UAAU,EAAE,CAAC,UAA8B,EAAE,EAAE,CAC7C,sDAAsD,UAAU,IAAI,eAAe,EAAE;QACvF,SAAS,EAAE,CAAC,UAAkB,EAAE,EAAE,CAChC,uDAAuD,UAAU,IAAI,eAAe,EAAE;QACxF,QAAQ,EAAE,CAAC,UAAkB,EAAE,EAAE,CAC/B,wCAAwC,UAAU,EAAE;QACtD,KAAK,EAAE,CAAC,UAAkB,EAAE,EAAE,CAC5B,kEAAkE,UAAU,EAAE;QAChF,SAAS,EAAE,kCAAkC;KAC9C;CACO,CAAA;AAEV,gBAAgB;AAChB,MAAM,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA"}
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { raceSearch } from './search.js';
3
+ import { filterResultFields } from './utils.js';
4
+ export { raceSearch, filterResultFields };
5
+ export { parseSearchResults } from './parser.js';
6
+ export { fetchResults, fetchMultiplePages } from './search.js';
7
+ export { shuffleAndTake } from './utils.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAgBA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAgJ/C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import { FastMCP, UserError } from 'fastmcp';
3
+ import { z } from 'zod';
4
+ import { BASE_URLS, BATCH_SIZE, MIN_SERVERS, DEFAULT_ENGINES, DEFAULT_SAFESARCH, DEFAULT_LANGUAGE, DEFAULT_PAGES, DEFAULT_CATEGORIES, DEFAULT_TIME_RANGE, opt, } from './config.js';
5
+ import { raceSearch } from './search.js';
6
+ import { filterResultFields } from './utils.js';
7
+ import { t, CURRENT_LANG } from './i18n.js';
8
+ const VERSION = '1.0.0';
9
+ /** 控制台 Logger(用于调试) */
10
+ class ConsoleLogger {
11
+ debug(...args) {
12
+ console.debug('[DEBUG]', new Date().toISOString(), ...args);
13
+ }
14
+ error(...args) {
15
+ console.error('[ERROR]', new Date().toISOString(), ...args);
16
+ }
17
+ info(...args) {
18
+ console.info('[INFO]', new Date().toISOString(), ...args);
19
+ }
20
+ log(...args) {
21
+ console.log('[LOG]', new Date().toISOString(), ...args);
22
+ }
23
+ warn(...args) {
24
+ console.warn('[WARN]', new Date().toISOString(), ...args);
25
+ }
26
+ }
27
+ // ============ MCP 服务器 ============
28
+ function startServer() {
29
+ // 启动时打印配置信息
30
+ console.log('[INFO] SearXNG MCP Server 启动');
31
+ console.log('[INFO] 配置:', {
32
+ BASE_URLS,
33
+ BATCH_SIZE,
34
+ MIN_SERVERS,
35
+ DEFAULT_ENGINES,
36
+ DEFAULT_LANGUAGE,
37
+ LANG: CURRENT_LANG,
38
+ });
39
+ const server = new FastMCP({
40
+ name: 'SearXNG Search',
41
+ version: VERSION,
42
+ logger: new ConsoleLogger(),
43
+ });
44
+ server.addTool({
45
+ name: 'search',
46
+ description: t.toolDescription,
47
+ parameters: z.object({
48
+ query: z.string().describe(t.query),
49
+ ...opt('categories', z.string().describe(t.categories(DEFAULT_CATEGORIES)).optional()),
50
+ ...opt('engines', z.string().describe(t.engines(DEFAULT_ENGINES)).optional()),
51
+ ...opt('safesearch', z
52
+ .number()
53
+ .min(0)
54
+ .max(2)
55
+ .describe(t.safesearch(DEFAULT_SAFESARCH))
56
+ .optional()),
57
+ ...opt('time_range', z.string().describe(t.timeRange(DEFAULT_TIME_RANGE)).optional()),
58
+ ...opt('language', z.string().describe(t.language(DEFAULT_LANGUAGE)).optional()),
59
+ ...opt('pages', z.number().min(1).max(5).describe(t.pages(DEFAULT_PAGES)).optional()),
60
+ ...opt('startpage', z.number().min(1).describe(t.startpage).optional()),
61
+ }),
62
+ annotations: {
63
+ readOnlyHint: true,
64
+ destructiveHint: false,
65
+ openWorldHint: true,
66
+ idempotentHint: false,
67
+ },
68
+ execute: async (params, { log }) => {
69
+ const { query, categories, engines, safesearch, time_range, language, pages, startpage, } = params;
70
+ try {
71
+ const results = await raceSearch(log, query, pages, startpage, {
72
+ categories,
73
+ engines,
74
+ safesearch,
75
+ timeRange: time_range,
76
+ language,
77
+ });
78
+ return {
79
+ content: [
80
+ {
81
+ type: 'text',
82
+ text: JSON.stringify(results.map(filterResultFields), null, 2),
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ catch (error) {
88
+ throw new UserError(`搜索失败: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ },
91
+ });
92
+ process.on('SIGINT', () => process.exit(0));
93
+ process.stdout.on('error', (err) => {
94
+ if (err.code === 'EPIPE') {
95
+ process.exit(0);
96
+ }
97
+ else {
98
+ throw err;
99
+ }
100
+ });
101
+ server.start({ transportType: 'stdio' });
102
+ }
103
+ if (!process.env.VITEST) {
104
+ startServer();
105
+ }
106
+ // 导出用于测试
107
+ export { raceSearch, filterResultFields };
108
+ export { parseSearchResults } from './parser.js';
109
+ export { fetchResults, fetchMultiplePages } from './search.js';
110
+ export { shuffleAndTake } from './utils.js';
111
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { SearchResult } from './types.js';
2
+ /**
3
+ * 从 HTML 中解析搜索结果
4
+ */
5
+ export declare function parseSearchResults(html: string, sourceServer: string): SearchResult[];
6
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAyD9C;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,GACnB,YAAY,EAAE,CAmChB"}