mcp-searxng 0.2.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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +168 -0
- package/package.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 IS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# SearXNG MCP Server
|
|
2
|
+
|
|
3
|
+
An [MCP server](https://modelcontextprotocol.io/introduction) implementation that integrates the SearxNG API, providing web search capabilities.
|
|
4
|
+
|
|
5
|
+
<a href="https://glama.ai/mcp/servers/0j7jjyt7m9"><img width="380" height="200" src="https://glama.ai/mcp/servers/0j7jjyt7m9/badge" alt="SearXNG Server MCP server" /></a>
|
|
6
|
+
|
|
7
|
+
[](https://smithery.ai/server/@ihor-sokoliuk/server-searxng)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Web Search**: General queries, news, articles, with pagination.
|
|
12
|
+
- **Pagination**: Control return size and result counts options.
|
|
13
|
+
|
|
14
|
+
## Tools
|
|
15
|
+
|
|
16
|
+
- **searxng_web_search**
|
|
17
|
+
- Execute web searches with pagination
|
|
18
|
+
- Inputs:
|
|
19
|
+
- `query` (string): Search terms
|
|
20
|
+
- `count` (number, optional): Results per page (default 20)
|
|
21
|
+
- `offset` (number, optional): Pagination offset (default 0)
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
### Setting the SEARXNG_URL
|
|
26
|
+
|
|
27
|
+
1. Choose a SearxNG instance from the [list of public instances](https://searx.space/) or use your local environment.
|
|
28
|
+
2. Set the `SEARXNG_URL` environment variable to the instance URL.
|
|
29
|
+
3. The default `SEARXNG_URL` value is `http://localhost:8080`.
|
|
30
|
+
|
|
31
|
+
### Usage with Claude Desktop
|
|
32
|
+
|
|
33
|
+
### Installing via Smithery
|
|
34
|
+
|
|
35
|
+
To install SearxNG Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@ihor-sokoliuk/server-searxng):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx -y @smithery/cli install @ihor-sokoliuk/server-searxng --client claude
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Installing via npm
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @ihor-sokoliuk/server-searxng
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
And then in your `claude_desktop_config.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"searxng": {
|
|
53
|
+
"command": "mcp-server-searxng",
|
|
54
|
+
"env": {
|
|
55
|
+
"SEARXNG_URL": "YOUR_SEARXNG_INSTANCE_URL"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### NPX
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"searxng": {
|
|
68
|
+
"command": "npx",
|
|
69
|
+
"args": [
|
|
70
|
+
"-y",
|
|
71
|
+
"@ihor-sokoliuk/server-searxng"
|
|
72
|
+
],
|
|
73
|
+
"env": {
|
|
74
|
+
"SEARXNG_URL": "YOUR_SEARXNG_INSTANCE_URL"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Docker
|
|
82
|
+
|
|
83
|
+
#### Build
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
docker build -t mcp-server-searxng:latest -f Dockerfile .
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### Use
|
|
90
|
+
|
|
91
|
+
Add this to your `claude_desktop_config.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"mcpServers": {
|
|
96
|
+
"searxng": {
|
|
97
|
+
"command": "docker",
|
|
98
|
+
"args": [
|
|
99
|
+
"run",
|
|
100
|
+
"-i",
|
|
101
|
+
"--rm",
|
|
102
|
+
"-e",
|
|
103
|
+
"SEARXNG_URL",
|
|
104
|
+
"mcp-server-searxng:latest"
|
|
105
|
+
],
|
|
106
|
+
"env": {
|
|
107
|
+
"SEARXNG_URL": "YOUR_SEARXNG_INSTANCE_URL"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Publishing to npm
|
|
115
|
+
|
|
116
|
+
To make your library publicly available on npm:
|
|
117
|
+
|
|
118
|
+
### 1. Create an npm Account
|
|
119
|
+
|
|
120
|
+
If you don't have an npm account, create one at [npmjs.com](https://www.npmjs.com/signup).
|
|
121
|
+
|
|
122
|
+
### 2. Log in to npm
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm login
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 3. Prepare your package
|
|
129
|
+
|
|
130
|
+
Make sure your package.json is correctly set up with:
|
|
131
|
+
- A unique name (`@ihor-sokoliuk/server-searxng`)
|
|
132
|
+
- Version number
|
|
133
|
+
- Description
|
|
134
|
+
- Entry point
|
|
135
|
+
- Dependencies
|
|
136
|
+
|
|
137
|
+
### 4. Build your package
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm run build
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 5. Publish to npm
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm publish --access=public
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For subsequent updates:
|
|
150
|
+
1. Update the version in package.json: `npm version patch|minor|major`
|
|
151
|
+
2. Run `npm publish`
|
|
152
|
+
|
|
153
|
+
### 6. Verify your package
|
|
154
|
+
|
|
155
|
+
Check that your package is available at `https://www.npmjs.com/package/@ihor-sokoliuk/server-searxng`
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { NodeHtmlMarkdown } from "node-html-markdown";
|
|
6
|
+
const WEB_SEARCH_TOOL = {
|
|
7
|
+
name: "searxng_web_search",
|
|
8
|
+
description: "Performs a web search using the SearXNG API, ideal for general queries, news, articles, and online content. " +
|
|
9
|
+
"Use this for broad information gathering, recent events, or when you need diverse web sources.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
query: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Search query",
|
|
16
|
+
},
|
|
17
|
+
count: {
|
|
18
|
+
type: "number",
|
|
19
|
+
description: "Number of results",
|
|
20
|
+
default: 20,
|
|
21
|
+
},
|
|
22
|
+
offset: {
|
|
23
|
+
type: "number",
|
|
24
|
+
description: "Pagination offset",
|
|
25
|
+
default: 0,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["query"],
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const READ_URL_TOOL = {
|
|
32
|
+
name: "web_url_read",
|
|
33
|
+
description: "Read the content from an URL. " +
|
|
34
|
+
"Use this for further information retrieving to understand the content of each URL.",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
url: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "URL",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ["url"],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
// Server implementation
|
|
47
|
+
const server = new Server({
|
|
48
|
+
name: "example-servers/searxng-search",
|
|
49
|
+
version: "0.1.0",
|
|
50
|
+
}, {
|
|
51
|
+
capabilities: {
|
|
52
|
+
resources: {},
|
|
53
|
+
tools: {},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
function isSearXNGWebSearchArgs(args) {
|
|
57
|
+
return (typeof args === "object" &&
|
|
58
|
+
args !== null &&
|
|
59
|
+
"query" in args &&
|
|
60
|
+
typeof args.query === "string");
|
|
61
|
+
}
|
|
62
|
+
async function performWebSearch(query, count = 10, offset = 0) {
|
|
63
|
+
const searxngUrl = process.env.SEARXNG_URL || "http://localhost:8080";
|
|
64
|
+
const url = new URL(`${searxngUrl}/search`);
|
|
65
|
+
url.searchParams.set("q", query);
|
|
66
|
+
url.searchParams.set("format", "json");
|
|
67
|
+
url.searchParams.set("start", offset.toString());
|
|
68
|
+
url.searchParams.set("count", count.toString());
|
|
69
|
+
const response = await fetch(url.toString(), {
|
|
70
|
+
method: "GET",
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`SearXNG API error: ${response.status} ${response.statusText}\n${await response.text()}`);
|
|
74
|
+
}
|
|
75
|
+
const data = (await response.json());
|
|
76
|
+
const results = (data.results || []).map((result) => ({
|
|
77
|
+
title: result.title || "",
|
|
78
|
+
content: result.content || "",
|
|
79
|
+
url: result.url || "",
|
|
80
|
+
}));
|
|
81
|
+
return results
|
|
82
|
+
.map((r) => `Title: ${r.title}\nDescription: ${r.content}\nURL: ${r.url}`)
|
|
83
|
+
.join("\n\n");
|
|
84
|
+
}
|
|
85
|
+
async function fetchAndConvertToMarkdown(url, timeoutMs = 10000) {
|
|
86
|
+
// Create an AbortController instance
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
89
|
+
try {
|
|
90
|
+
// Fetch the URL with the abort signal
|
|
91
|
+
const response = await fetch(url, {
|
|
92
|
+
signal: controller.signal,
|
|
93
|
+
});
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new Error(`Failed to fetch the URL: ${response.statusText}`);
|
|
96
|
+
}
|
|
97
|
+
// Retrieve HTML content
|
|
98
|
+
const htmlContent = await response.text();
|
|
99
|
+
// Convert HTML to Markdown
|
|
100
|
+
const markdownContent = NodeHtmlMarkdown.translate(htmlContent);
|
|
101
|
+
return markdownContent;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error.name === "AbortError") {
|
|
105
|
+
throw new Error(`Request timed out after ${timeoutMs}ms`);
|
|
106
|
+
}
|
|
107
|
+
console.error("Error:", error.message);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
// Clean up the timeout to prevent memory leaks
|
|
112
|
+
clearTimeout(timeoutId);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Tool handlers
|
|
116
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
117
|
+
tools: [WEB_SEARCH_TOOL, READ_URL_TOOL],
|
|
118
|
+
}));
|
|
119
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
120
|
+
try {
|
|
121
|
+
const { name, arguments: args } = request.params;
|
|
122
|
+
if (!args) {
|
|
123
|
+
throw new Error("No arguments provided");
|
|
124
|
+
}
|
|
125
|
+
if (name === "searxng_web_search") {
|
|
126
|
+
if (!isSearXNGWebSearchArgs(args)) {
|
|
127
|
+
throw new Error("Invalid arguments for searxng_web_search");
|
|
128
|
+
}
|
|
129
|
+
const { query, count = 10 } = args;
|
|
130
|
+
const results = await performWebSearch(query, count);
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: results }],
|
|
133
|
+
isError: false,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (name === "web_url_read") {
|
|
137
|
+
const { url } = args;
|
|
138
|
+
const result = await fetchAndConvertToMarkdown(url);
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: result }],
|
|
141
|
+
isError: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
146
|
+
isError: true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
isError: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
async function runServer() {
|
|
162
|
+
const transport = new StdioServerTransport();
|
|
163
|
+
await server.connect(transport);
|
|
164
|
+
}
|
|
165
|
+
runServer().catch((error) => {
|
|
166
|
+
console.error("Fatal error running server:", error);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-searxng",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for SearXNG integration",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ihor Sokoliuk (https://github.com/ihor-sokoliuk)",
|
|
7
|
+
"homepage": "https://github.com/ihor-sokoliuk/mcp-searxng",
|
|
8
|
+
"bugs": "https://github.com/ihor-sokoliuk/mcp-searxng/issues",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"mcp-searxng": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && shx chmod +x dist/*.js",
|
|
18
|
+
"prepare": "npm run build",
|
|
19
|
+
"watch": "tsc --watch"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "1.9.0",
|
|
23
|
+
"node-html-markdown": "^1.3.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.14.1",
|
|
27
|
+
"shx": "^0.3.4",
|
|
28
|
+
"typescript": "^5.7.2"
|
|
29
|
+
}
|
|
30
|
+
}
|