mcp-searxng 0.9.2 → 0.10.1
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 +78 -11
- package/dist/http-server.d.ts +2 -2
- package/dist/http-server.js +12 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +21 -28
- package/dist/logging.d.ts +2 -2
- package/dist/logging.js +4 -7
- package/dist/proxy.d.ts +27 -4
- package/dist/proxy.js +99 -5
- package/dist/search.d.ts +2 -2
- package/dist/search.js +9 -9
- package/dist/url-reader.d.ts +2 -2
- package/dist/url-reader.js +20 -12
- package/package.json +18 -14
package/README.md
CHANGED
|
@@ -49,12 +49,29 @@ An [MCP server](https://modelcontextprotocol.io/introduction) implementation tha
|
|
|
49
49
|
- Example: `https://search.example.com`
|
|
50
50
|
|
|
51
51
|
#### Optional
|
|
52
|
-
- **`AUTH_USERNAME`** / **`AUTH_PASSWORD`**: HTTP Basic Auth credentials for password-protected instances
|
|
53
|
-
- **`USER_AGENT`**:
|
|
54
|
-
- **`
|
|
52
|
+
- **`AUTH_USERNAME`** / **`AUTH_PASSWORD`**: HTTP Basic Auth credentials for `searxng_web_search` (password-protected SearXNG instances)
|
|
53
|
+
- **`USER_AGENT`**: Global default User-Agent header used by both `searxng_web_search` and `web_url_read` (e.g., `MyBot/1.0`)
|
|
54
|
+
- **`URL_READER_USER_AGENT`**: Custom User-Agent specifically for the `web_url_read` tool (overrides `USER_AGENT` for URL reading requests)
|
|
55
|
+
- **`HTTP_PROXY`** / **`HTTPS_PROXY`**: Global proxy URLs for routing traffic (fallback for both interfaces)
|
|
55
56
|
- Format: `http://[username:password@]proxy.host:port`
|
|
56
57
|
- **`NO_PROXY`**: Comma-separated bypass list (e.g., `localhost,.internal,example.com`)
|
|
57
58
|
|
|
59
|
+
##### Interface-Specific Proxies (Optional)
|
|
60
|
+
- **`SEARCH_HTTP_PROXY`** / **`SEARCH_HTTPS_PROXY`**: Proxy for `searxng_web_search` tool only
|
|
61
|
+
- **`URL_READER_HTTP_PROXY`** / **`URL_READER_HTTPS_PROXY`**: Proxy for `web_url_read` tool only
|
|
62
|
+
- These take priority over `HTTP_PROXY`/`HTTPS_PROXY` for their respective interfaces
|
|
63
|
+
|
|
64
|
+
#### Advanced Configuration
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Separate proxies for search and URL reading
|
|
68
|
+
SEARCH_HTTP_PROXY=http://search-proxy:8080
|
|
69
|
+
URL_READER_HTTP_PROXY=http://reader-proxy:8080
|
|
70
|
+
|
|
71
|
+
# Custom user_agent for URL reader
|
|
72
|
+
URL_READER_USER_AGENT="Mozilla/5.0 (compatible; Bot/1.0)"
|
|
73
|
+
```
|
|
74
|
+
|
|
58
75
|
## Installation & Configuration
|
|
59
76
|
|
|
60
77
|
### [NPX](https://www.npmjs.com/package/mcp-searxng)
|
|
@@ -87,8 +104,11 @@ An [MCP server](https://modelcontextprotocol.io/introduction) implementation tha
|
|
|
87
104
|
"AUTH_USERNAME": "your_username",
|
|
88
105
|
"AUTH_PASSWORD": "your_password",
|
|
89
106
|
"USER_AGENT": "MyBot/1.0",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
107
|
+
"URL_READER_USER_AGENT": "Mozilla/5.0 (compatible; MyBot/1.0)",
|
|
108
|
+
"SEARCH_HTTP_PROXY": "http://search-proxy.company.com:8080",
|
|
109
|
+
"URL_READER_HTTP_PROXY": "http://reader-proxy.company.com:8080",
|
|
110
|
+
"HTTP_PROXY": "http://global-proxy.company.com:8080",
|
|
111
|
+
"HTTPS_PROXY": "http://global-proxy.company.com:8080",
|
|
92
112
|
"NO_PROXY": "localhost,127.0.0.1,.local,.internal"
|
|
93
113
|
}
|
|
94
114
|
}
|
|
@@ -132,15 +152,17 @@ npm install -g mcp-searxng
|
|
|
132
152
|
"AUTH_USERNAME": "your_username",
|
|
133
153
|
"AUTH_PASSWORD": "your_password",
|
|
134
154
|
"USER_AGENT": "MyBot/1.0",
|
|
135
|
-
"
|
|
136
|
-
"
|
|
155
|
+
"URL_READER_USER_AGENT": "Mozilla/5.0 (compatible; MyBot/1.0)",
|
|
156
|
+
"SEARCH_HTTP_PROXY": "http://search-proxy.company.com:8080",
|
|
157
|
+
"URL_READER_HTTP_PROXY": "http://reader-proxy.company.com:8080",
|
|
158
|
+
"HTTP_PROXY": "http://global-proxy.company.com:8080",
|
|
159
|
+
"HTTPS_PROXY": "http://global-proxy.company.com:8080",
|
|
137
160
|
"NO_PROXY": "localhost,127.0.0.1,.local,.internal"
|
|
138
161
|
}
|
|
139
162
|
}
|
|
140
163
|
}
|
|
141
164
|
}
|
|
142
165
|
```
|
|
143
|
-
|
|
144
166
|
</details>
|
|
145
167
|
|
|
146
168
|
### Docker
|
|
@@ -183,6 +205,11 @@ docker pull isokoliuk/mcp-searxng:latest
|
|
|
183
205
|
"-e", "AUTH_USERNAME",
|
|
184
206
|
"-e", "AUTH_PASSWORD",
|
|
185
207
|
"-e", "USER_AGENT",
|
|
208
|
+
"-e", "URL_READER_USER_AGENT",
|
|
209
|
+
"-e", "SEARCH_HTTP_PROXY",
|
|
210
|
+
"-e", "SEARCH_HTTPS_PROXY",
|
|
211
|
+
"-e", "URL_READER_HTTP_PROXY",
|
|
212
|
+
"-e", "URL_READER_HTTPS_PROXY",
|
|
186
213
|
"-e", "HTTP_PROXY",
|
|
187
214
|
"-e", "HTTPS_PROXY",
|
|
188
215
|
"-e", "NO_PROXY",
|
|
@@ -193,8 +220,11 @@ docker pull isokoliuk/mcp-searxng:latest
|
|
|
193
220
|
"AUTH_USERNAME": "your_username",
|
|
194
221
|
"AUTH_PASSWORD": "your_password",
|
|
195
222
|
"USER_AGENT": "MyBot/1.0",
|
|
196
|
-
"
|
|
197
|
-
"
|
|
223
|
+
"URL_READER_USER_AGENT": "Mozilla/5.0 (compatible; MyBot/1.0)",
|
|
224
|
+
"SEARCH_HTTP_PROXY": "http://search-proxy.company.com:8080",
|
|
225
|
+
"URL_READER_HTTP_PROXY": "http://reader-proxy.company.com:8080",
|
|
226
|
+
"HTTP_PROXY": "http://global-proxy.company.com:8080",
|
|
227
|
+
"HTTPS_PROXY": "http://global-proxy.company.com:8080",
|
|
198
228
|
"NO_PROXY": "localhost,127.0.0.1,.local,.internal"
|
|
199
229
|
}
|
|
200
230
|
}
|
|
@@ -229,7 +259,10 @@ services:
|
|
|
229
259
|
# - AUTH_USERNAME=your_username
|
|
230
260
|
# - AUTH_PASSWORD=your_password
|
|
231
261
|
# - USER_AGENT=MyBot/1.0
|
|
232
|
-
# -
|
|
262
|
+
# - URL_READER_USER_AGENT=Mozilla/5.0 (compatible; MyBot/1.0)
|
|
263
|
+
# - SEARCH_HTTP_PROXY=http://search-proxy.company.com:8080
|
|
264
|
+
# - URL_READER_HTTP_PROXY=http://reader-proxy.company.com:8080
|
|
265
|
+
# - HTTP_PROXY=http://global-proxy.company.com:8080
|
|
233
266
|
# - HTTPS_PROXY=http://proxy.company.com:8080
|
|
234
267
|
# - NO_PROXY=localhost,127.0.0.1,.local,.internal
|
|
235
268
|
```
|
|
@@ -275,6 +308,40 @@ MCP_HTTP_PORT=3000 SEARXNG_URL=http://localhost:8080 mcp-searxng
|
|
|
275
308
|
curl http://localhost:3000/health
|
|
276
309
|
```
|
|
277
310
|
|
|
311
|
+
## Troubleshooting
|
|
312
|
+
|
|
313
|
+
### 403 Forbidden Error from SearXNG
|
|
314
|
+
|
|
315
|
+
If you receive a `403 Forbidden` error when using `mcp-searxng`, it is likely because your SearXNG instance does not have JSON format enabled. This server requests results in JSON format (`format=json`), which must be explicitly allowed in SearXNG's configuration.
|
|
316
|
+
|
|
317
|
+
**To fix this**, edit your SearXNG `settings.yml` (commonly located at `/etc/searxng/settings.yml`) and add `json` to the list of allowed formats:
|
|
318
|
+
|
|
319
|
+
```yaml
|
|
320
|
+
search:
|
|
321
|
+
formats:
|
|
322
|
+
- html
|
|
323
|
+
- json
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
After saving the file, restart your SearXNG instance. For example, if running with Docker:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
docker restart searxng
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
You can verify JSON format is working by running:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
curl 'http://localhost:8080/search?q=test&format=json'
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
You should receive a JSON response. If you still get a 403 error, double-check that:
|
|
339
|
+
- The `settings.yml` file is correctly mounted into your Docker container
|
|
340
|
+
- The YAML indentation is correct
|
|
341
|
+
- The SearXNG instance was fully restarted after the configuration change
|
|
342
|
+
|
|
343
|
+
For more details, see the [SearXNG settings documentation](https://docs.searxng.org/admin/settings/settings.html) and [this discussion](https://github.com/searxng/searxng/discussions/1789).
|
|
344
|
+
|
|
278
345
|
## Running evals
|
|
279
346
|
|
|
280
347
|
```bash
|
package/dist/http-server.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
-
import {
|
|
3
|
-
export declare function createHttpServer(
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
export declare function createHttpServer(mcpServer: McpServer): Promise<express.Application>;
|
package/dist/http-server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
5
5
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
6
6
|
import { logMessage } from "./logging.js";
|
|
7
7
|
import { packageVersion } from "./index.js";
|
|
8
|
-
export async function createHttpServer(
|
|
8
|
+
export async function createHttpServer(mcpServer) {
|
|
9
9
|
const app = express();
|
|
10
10
|
app.use(express.json());
|
|
11
11
|
// Add CORS support for web clients
|
|
@@ -23,16 +23,16 @@ export async function createHttpServer(server) {
|
|
|
23
23
|
if (sessionId && transports[sessionId]) {
|
|
24
24
|
// Reuse existing transport
|
|
25
25
|
transport = transports[sessionId];
|
|
26
|
-
logMessage(
|
|
26
|
+
logMessage(mcpServer, "debug", `Reusing session: ${sessionId}`);
|
|
27
27
|
}
|
|
28
28
|
else if (!sessionId && isInitializeRequest(req.body)) {
|
|
29
29
|
// New initialization request
|
|
30
|
-
logMessage(
|
|
30
|
+
logMessage(mcpServer, "info", "Creating new HTTP session");
|
|
31
31
|
transport = new StreamableHTTPServerTransport({
|
|
32
32
|
sessionIdGenerator: () => randomUUID(),
|
|
33
33
|
onsessioninitialized: (sessionId) => {
|
|
34
34
|
transports[sessionId] = transport;
|
|
35
|
-
logMessage(
|
|
35
|
+
logMessage(mcpServer, "debug", `Session initialized: ${sessionId}`);
|
|
36
36
|
},
|
|
37
37
|
// DNS rebinding protection disabled by default for backwards compatibility
|
|
38
38
|
// For production, consider enabling:
|
|
@@ -42,17 +42,17 @@ export async function createHttpServer(server) {
|
|
|
42
42
|
// Clean up transport when closed
|
|
43
43
|
transport.onclose = () => {
|
|
44
44
|
if (transport.sessionId) {
|
|
45
|
-
logMessage(
|
|
45
|
+
logMessage(mcpServer, "debug", `Session closed: ${transport.sessionId}`);
|
|
46
46
|
delete transports[transport.sessionId];
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
49
|
// Connect the existing server to the new transport
|
|
50
|
-
await
|
|
50
|
+
await mcpServer.connect(transport);
|
|
51
51
|
}
|
|
52
52
|
else {
|
|
53
53
|
// Invalid request
|
|
54
54
|
console.warn(`⚠️ POST request rejected - invalid request:`, {
|
|
55
|
-
clientIP: req.ip || req.
|
|
55
|
+
clientIP: req.ip || req.socket.remoteAddress,
|
|
56
56
|
sessionId: sessionId || 'undefined',
|
|
57
57
|
hasInitializeRequest: isInitializeRequest(req.body),
|
|
58
58
|
userAgent: req.headers['user-agent'],
|
|
@@ -77,7 +77,7 @@ export async function createHttpServer(server) {
|
|
|
77
77
|
// Log header-related rejections for debugging
|
|
78
78
|
if (error instanceof Error && error.message.includes('accept')) {
|
|
79
79
|
console.warn(`⚠️ Connection rejected due to missing headers:`, {
|
|
80
|
-
clientIP: req.ip || req.
|
|
80
|
+
clientIP: req.ip || req.socket.remoteAddress,
|
|
81
81
|
userAgent: req.headers['user-agent'],
|
|
82
82
|
contentType: req.headers['content-type'],
|
|
83
83
|
accept: req.headers['accept'],
|
|
@@ -92,7 +92,7 @@ export async function createHttpServer(server) {
|
|
|
92
92
|
const sessionId = req.headers['mcp-session-id'];
|
|
93
93
|
if (!sessionId || !transports[sessionId]) {
|
|
94
94
|
console.warn(`⚠️ GET request rejected - missing or invalid session ID:`, {
|
|
95
|
-
clientIP: req.ip || req.
|
|
95
|
+
clientIP: req.ip || req.socket.remoteAddress,
|
|
96
96
|
sessionId: sessionId || 'undefined',
|
|
97
97
|
userAgent: req.headers['user-agent']
|
|
98
98
|
});
|
|
@@ -105,7 +105,7 @@ export async function createHttpServer(server) {
|
|
|
105
105
|
}
|
|
106
106
|
catch (error) {
|
|
107
107
|
console.warn(`⚠️ GET request failed:`, {
|
|
108
|
-
clientIP: req.ip || req.
|
|
108
|
+
clientIP: req.ip || req.socket.remoteAddress,
|
|
109
109
|
sessionId,
|
|
110
110
|
error: error instanceof Error ? error.message : String(error)
|
|
111
111
|
});
|
|
@@ -117,7 +117,7 @@ export async function createHttpServer(server) {
|
|
|
117
117
|
const sessionId = req.headers['mcp-session-id'];
|
|
118
118
|
if (!sessionId || !transports[sessionId]) {
|
|
119
119
|
console.warn(`⚠️ DELETE request rejected - missing or invalid session ID:`, {
|
|
120
|
-
clientIP: req.ip || req.
|
|
120
|
+
clientIP: req.ip || req.socket.remoteAddress,
|
|
121
121
|
sessionId: sessionId || 'undefined',
|
|
122
122
|
userAgent: req.headers['user-agent']
|
|
123
123
|
});
|
|
@@ -130,7 +130,7 @@ export async function createHttpServer(server) {
|
|
|
130
130
|
}
|
|
131
131
|
catch (error) {
|
|
132
132
|
console.warn(`⚠️ DELETE request failed:`, {
|
|
133
|
-
clientIP: req.ip || req.
|
|
133
|
+
clientIP: req.ip || req.socket.remoteAddress,
|
|
134
134
|
sessionId,
|
|
135
135
|
error: error instanceof Error ? error.message : String(error)
|
|
136
136
|
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
// Import modularized functionality
|
|
@@ -11,7 +11,7 @@ import { createConfigResource, createHelpResource } from "./resources.js";
|
|
|
11
11
|
import { createHttpServer } from "./http-server.js";
|
|
12
12
|
import { validateEnvironment as validateEnv } from "./error-handler.js";
|
|
13
13
|
// Use a static version string that will be updated by the version script
|
|
14
|
-
const packageVersion = "0.
|
|
14
|
+
const packageVersion = "0.10.1";
|
|
15
15
|
// Export the version for use in other modules
|
|
16
16
|
export { packageVersion };
|
|
17
17
|
// Global state for logging level
|
|
@@ -49,28 +49,21 @@ export function isWebUrlReadArgs(args) {
|
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
51
|
// Server implementation
|
|
52
|
-
const
|
|
52
|
+
const mcpServer = new McpServer({
|
|
53
53
|
name: "ihor-sokoliuk/mcp-searxng",
|
|
54
54
|
version: packageVersion,
|
|
55
55
|
}, {
|
|
56
56
|
capabilities: {
|
|
57
57
|
logging: {},
|
|
58
58
|
resources: {},
|
|
59
|
-
tools: {
|
|
60
|
-
searxng_web_search: {
|
|
61
|
-
description: WEB_SEARCH_TOOL.description,
|
|
62
|
-
schema: WEB_SEARCH_TOOL.inputSchema,
|
|
63
|
-
},
|
|
64
|
-
web_url_read: {
|
|
65
|
-
description: READ_URL_TOOL.description,
|
|
66
|
-
schema: READ_URL_TOOL.inputSchema,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
59
|
+
tools: {},
|
|
69
60
|
},
|
|
70
61
|
});
|
|
62
|
+
// Underlying low-level server for handler registration and passing to modules
|
|
63
|
+
const server = mcpServer.server;
|
|
71
64
|
// List tools handler
|
|
72
65
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
73
|
-
logMessage(
|
|
66
|
+
logMessage(mcpServer, "debug", "Handling list_tools request");
|
|
74
67
|
return {
|
|
75
68
|
tools: [WEB_SEARCH_TOOL, READ_URL_TOOL],
|
|
76
69
|
};
|
|
@@ -78,13 +71,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
78
71
|
// Call tool handler
|
|
79
72
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
80
73
|
const { name, arguments: args } = request.params;
|
|
81
|
-
logMessage(
|
|
74
|
+
logMessage(mcpServer, "debug", `Handling call_tool request: ${name}`);
|
|
82
75
|
try {
|
|
83
76
|
if (name === "searxng_web_search") {
|
|
84
77
|
if (!isSearXNGWebSearchArgs(args)) {
|
|
85
78
|
throw new Error("Invalid arguments for web search");
|
|
86
79
|
}
|
|
87
|
-
const result = await performWebSearch(
|
|
80
|
+
const result = await performWebSearch(mcpServer, args.query, args.pageno, args.time_range, args.language, args.safesearch);
|
|
88
81
|
return {
|
|
89
82
|
content: [
|
|
90
83
|
{
|
|
@@ -105,7 +98,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
105
98
|
paragraphRange: args.paragraphRange,
|
|
106
99
|
readHeadings: args.readHeadings,
|
|
107
100
|
};
|
|
108
|
-
const result = await fetchAndConvertToMarkdown(
|
|
101
|
+
const result = await fetchAndConvertToMarkdown(mcpServer, args.url, 10000, paginationOptions);
|
|
109
102
|
return {
|
|
110
103
|
content: [
|
|
111
104
|
{
|
|
@@ -120,7 +113,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
120
113
|
}
|
|
121
114
|
}
|
|
122
115
|
catch (error) {
|
|
123
|
-
logMessage(
|
|
116
|
+
logMessage(mcpServer, "error", `Tool execution error: ${error instanceof Error ? error.message : String(error)}`, {
|
|
124
117
|
tool: name,
|
|
125
118
|
args: args,
|
|
126
119
|
error: error instanceof Error ? error.stack : String(error)
|
|
@@ -131,14 +124,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
131
124
|
// Logging level handler
|
|
132
125
|
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
|
|
133
126
|
const { level } = request.params;
|
|
134
|
-
logMessage(
|
|
127
|
+
logMessage(mcpServer, "info", `Setting log level to: ${level}`);
|
|
135
128
|
currentLogLevel = level;
|
|
136
129
|
setLogLevel(level);
|
|
137
130
|
return {};
|
|
138
131
|
});
|
|
139
132
|
// List resources handler
|
|
140
133
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
141
|
-
logMessage(
|
|
134
|
+
logMessage(mcpServer, "debug", "Handling list_resources request");
|
|
142
135
|
return {
|
|
143
136
|
resources: [
|
|
144
137
|
{
|
|
@@ -158,13 +151,13 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
158
151
|
});
|
|
159
152
|
// List resource templates handler
|
|
160
153
|
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
161
|
-
logMessage(
|
|
154
|
+
logMessage(mcpServer, "debug", "Handling list_resource_templates request");
|
|
162
155
|
return { resourceTemplates: [] };
|
|
163
156
|
});
|
|
164
157
|
// Read resource handler
|
|
165
158
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
166
159
|
const { uri } = request.params;
|
|
167
|
-
logMessage(
|
|
160
|
+
logMessage(mcpServer, "debug", `Handling read_resource request for: ${uri}`);
|
|
168
161
|
switch (uri) {
|
|
169
162
|
case "config://server-config":
|
|
170
163
|
return {
|
|
@@ -207,7 +200,7 @@ async function main() {
|
|
|
207
200
|
process.exit(1);
|
|
208
201
|
}
|
|
209
202
|
console.log(`Starting HTTP transport on port ${port}`);
|
|
210
|
-
const app = await createHttpServer(
|
|
203
|
+
const app = await createHttpServer(mcpServer);
|
|
211
204
|
const httpServer = app.listen(port, () => {
|
|
212
205
|
console.log(`HTTP server listening on port ${port}`);
|
|
213
206
|
console.log(`Health check: http://localhost:${port}/health`);
|
|
@@ -234,12 +227,12 @@ async function main() {
|
|
|
234
227
|
console.error("📡 Waiting for MCP client connection via STDIO...\n");
|
|
235
228
|
}
|
|
236
229
|
const transport = new StdioServerTransport();
|
|
237
|
-
await
|
|
230
|
+
await mcpServer.connect(transport);
|
|
238
231
|
// Log after connection is established
|
|
239
|
-
logMessage(
|
|
240
|
-
logMessage(
|
|
241
|
-
logMessage(
|
|
242
|
-
logMessage(
|
|
232
|
+
logMessage(mcpServer, "info", `MCP SearXNG Server v${packageVersion} connected via STDIO`);
|
|
233
|
+
logMessage(mcpServer, "info", `Log level: ${currentLogLevel}`);
|
|
234
|
+
logMessage(mcpServer, "info", `Environment: ${process.env.NODE_ENV || 'development'}`);
|
|
235
|
+
logMessage(mcpServer, "info", `SearXNG URL: ${process.env.SEARXNG_URL || 'not configured'}`);
|
|
243
236
|
}
|
|
244
237
|
}
|
|
245
238
|
// Handle uncaught errors
|
package/dist/logging.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
-
export declare function logMessage(
|
|
3
|
+
export declare function logMessage(mcpServer: McpServer, level: LoggingLevel, message: string, data?: unknown): void;
|
|
4
4
|
export declare function shouldLog(level: LoggingLevel): boolean;
|
|
5
5
|
export declare function setLogLevel(level: LoggingLevel): void;
|
|
6
6
|
export declare function getCurrentLogLevel(): LoggingLevel;
|
package/dist/logging.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
// Logging state
|
|
2
2
|
let currentLogLevel = "info";
|
|
3
3
|
// Logging helper function
|
|
4
|
-
export function logMessage(
|
|
4
|
+
export function logMessage(mcpServer, level, message, data) {
|
|
5
5
|
if (shouldLog(level)) {
|
|
6
6
|
try {
|
|
7
7
|
// Merge message and data together for the notification body
|
|
8
8
|
const notificationData = data !== undefined
|
|
9
9
|
? (typeof data === 'object' && data !== null ? { message, ...data } : { message, data })
|
|
10
10
|
: { message };
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
level,
|
|
15
|
-
data: notificationData
|
|
16
|
-
}
|
|
11
|
+
mcpServer.sendLoggingMessage({
|
|
12
|
+
level,
|
|
13
|
+
data: notificationData
|
|
17
14
|
}).catch((error) => {
|
|
18
15
|
// Silently ignore "Not connected" errors during server startup
|
|
19
16
|
// This can happen when logging occurs before the transport is fully connected
|
package/dist/proxy.d.ts
CHANGED
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
import { ProxyAgent } from "undici";
|
|
2
|
+
/**
|
|
3
|
+
* Proxy configuration type for separating search and URL reader proxies.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ProxyType: {
|
|
6
|
+
readonly SEARCH: "search";
|
|
7
|
+
readonly URL_READER: "url_reader";
|
|
8
|
+
};
|
|
9
|
+
export type ProxyType = typeof ProxyType[keyof typeof ProxyType];
|
|
2
10
|
/**
|
|
3
11
|
* Creates a proxy agent dispatcher for Node.js fetch API.
|
|
4
12
|
*
|
|
5
13
|
* Node.js fetch uses Undici under the hood, which requires a 'dispatcher' option
|
|
6
14
|
* instead of 'agent'. This function creates a ProxyAgent compatible with fetch.
|
|
7
15
|
*
|
|
8
|
-
* Environment variables checked (in order):
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
16
|
+
* Environment variables checked (in order, depending on URL protocol):
|
|
17
|
+
* - For type 'search' and HTTPS URLs:
|
|
18
|
+
* SEARCH_HTTPS_PROXY, SEARCH_HTTP_PROXY, search_https_proxy, search_http_proxy,
|
|
19
|
+
* then HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy
|
|
20
|
+
* - For type 'search' and HTTP/unknown URLs:
|
|
21
|
+
* SEARCH_HTTP_PROXY, SEARCH_HTTPS_PROXY, search_http_proxy, search_https_proxy,
|
|
22
|
+
* then HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy
|
|
23
|
+
* - For type 'url_reader' and HTTPS URLs:
|
|
24
|
+
* URL_READER_HTTPS_PROXY, URL_READER_HTTP_PROXY, url_reader_https_proxy, url_reader_http_proxy,
|
|
25
|
+
* then HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy
|
|
26
|
+
* - For type 'url_reader' and HTTP/unknown URLs:
|
|
27
|
+
* URL_READER_HTTP_PROXY, URL_READER_HTTPS_PROXY, url_reader_http_proxy, url_reader_https_proxy,
|
|
28
|
+
* then HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy
|
|
29
|
+
* - For no specific type and HTTPS URLs:
|
|
30
|
+
* HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy
|
|
31
|
+
* - For no specific type and HTTP/unknown URLs:
|
|
32
|
+
* HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy
|
|
11
33
|
* - NO_PROXY / no_proxy: Comma-separated list of hosts to bypass proxy
|
|
12
34
|
*
|
|
13
35
|
* @param targetUrl - Optional target URL to check against NO_PROXY rules
|
|
36
|
+
* @param type - Optional proxy type ('search' or 'url_reader') for separate proxy configs
|
|
14
37
|
* @returns ProxyAgent dispatcher for fetch, or undefined if no proxy configured or bypassed
|
|
15
38
|
*/
|
|
16
|
-
export declare function createProxyAgent(targetUrl?: string): ProxyAgent | undefined;
|
|
39
|
+
export declare function createProxyAgent(targetUrl?: string, type?: ProxyType): ProxyAgent | undefined;
|
package/dist/proxy.js
CHANGED
|
@@ -50,22 +50,116 @@ function shouldBypassProxy(targetUrl) {
|
|
|
50
50
|
}
|
|
51
51
|
return false;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Proxy configuration type for separating search and URL reader proxies.
|
|
55
|
+
*/
|
|
56
|
+
export const ProxyType = {
|
|
57
|
+
SEARCH: 'search',
|
|
58
|
+
URL_READER: 'url_reader',
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Gets proxy URL for the specified proxy type.
|
|
62
|
+
* Checks type-specific proxy first, then falls back to global proxy.
|
|
63
|
+
*
|
|
64
|
+
* @param type - The type of proxy to get ('search' or 'url_reader')
|
|
65
|
+
* @param targetUrl - Optional target URL whose protocol is used to select between HTTP and HTTPS proxies
|
|
66
|
+
* @returns The proxy URL or undefined if not configured
|
|
67
|
+
*/
|
|
68
|
+
function getProxyUrl(type, targetUrl) {
|
|
69
|
+
let isHttps = false;
|
|
70
|
+
if (targetUrl) {
|
|
71
|
+
try {
|
|
72
|
+
const url = new URL(targetUrl);
|
|
73
|
+
isHttps = url.protocol === 'https:';
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
isHttps = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (type === ProxyType.SEARCH) {
|
|
80
|
+
if (isHttps) {
|
|
81
|
+
return process.env.SEARCH_HTTPS_PROXY ||
|
|
82
|
+
process.env.SEARCH_HTTP_PROXY ||
|
|
83
|
+
process.env.search_https_proxy ||
|
|
84
|
+
process.env.search_http_proxy ||
|
|
85
|
+
process.env.HTTPS_PROXY ||
|
|
86
|
+
process.env.HTTP_PROXY ||
|
|
87
|
+
process.env.https_proxy ||
|
|
88
|
+
process.env.http_proxy;
|
|
89
|
+
}
|
|
90
|
+
return process.env.SEARCH_HTTP_PROXY ||
|
|
91
|
+
process.env.SEARCH_HTTPS_PROXY ||
|
|
92
|
+
process.env.search_http_proxy ||
|
|
93
|
+
process.env.search_https_proxy ||
|
|
94
|
+
// Fallback to global proxies
|
|
95
|
+
process.env.HTTP_PROXY ||
|
|
96
|
+
process.env.HTTPS_PROXY ||
|
|
97
|
+
process.env.http_proxy ||
|
|
98
|
+
process.env.https_proxy;
|
|
99
|
+
}
|
|
100
|
+
if (type === ProxyType.URL_READER) {
|
|
101
|
+
if (isHttps) {
|
|
102
|
+
return process.env.URL_READER_HTTPS_PROXY ||
|
|
103
|
+
process.env.URL_READER_HTTP_PROXY ||
|
|
104
|
+
process.env.url_reader_https_proxy ||
|
|
105
|
+
process.env.url_reader_http_proxy ||
|
|
106
|
+
process.env.HTTPS_PROXY ||
|
|
107
|
+
process.env.HTTP_PROXY ||
|
|
108
|
+
process.env.https_proxy ||
|
|
109
|
+
process.env.http_proxy;
|
|
110
|
+
}
|
|
111
|
+
return process.env.URL_READER_HTTP_PROXY ||
|
|
112
|
+
process.env.URL_READER_HTTPS_PROXY ||
|
|
113
|
+
process.env.url_reader_http_proxy ||
|
|
114
|
+
process.env.url_reader_https_proxy ||
|
|
115
|
+
// Fallback to global proxies
|
|
116
|
+
process.env.HTTP_PROXY ||
|
|
117
|
+
process.env.HTTPS_PROXY ||
|
|
118
|
+
process.env.http_proxy ||
|
|
119
|
+
process.env.https_proxy;
|
|
120
|
+
}
|
|
121
|
+
if (isHttps) {
|
|
122
|
+
return process.env.HTTPS_PROXY ||
|
|
123
|
+
process.env.HTTP_PROXY ||
|
|
124
|
+
process.env.https_proxy ||
|
|
125
|
+
process.env.http_proxy;
|
|
126
|
+
}
|
|
127
|
+
return process.env.HTTP_PROXY ||
|
|
128
|
+
process.env.HTTPS_PROXY ||
|
|
129
|
+
process.env.http_proxy ||
|
|
130
|
+
process.env.https_proxy;
|
|
131
|
+
}
|
|
53
132
|
/**
|
|
54
133
|
* Creates a proxy agent dispatcher for Node.js fetch API.
|
|
55
134
|
*
|
|
56
135
|
* Node.js fetch uses Undici under the hood, which requires a 'dispatcher' option
|
|
57
136
|
* instead of 'agent'. This function creates a ProxyAgent compatible with fetch.
|
|
58
137
|
*
|
|
59
|
-
* Environment variables checked (in order):
|
|
60
|
-
* -
|
|
61
|
-
*
|
|
138
|
+
* Environment variables checked (in order, depending on URL protocol):
|
|
139
|
+
* - For type 'search' and HTTPS URLs:
|
|
140
|
+
* SEARCH_HTTPS_PROXY, SEARCH_HTTP_PROXY, search_https_proxy, search_http_proxy,
|
|
141
|
+
* then HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy
|
|
142
|
+
* - For type 'search' and HTTP/unknown URLs:
|
|
143
|
+
* SEARCH_HTTP_PROXY, SEARCH_HTTPS_PROXY, search_http_proxy, search_https_proxy,
|
|
144
|
+
* then HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy
|
|
145
|
+
* - For type 'url_reader' and HTTPS URLs:
|
|
146
|
+
* URL_READER_HTTPS_PROXY, URL_READER_HTTP_PROXY, url_reader_https_proxy, url_reader_http_proxy,
|
|
147
|
+
* then HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy
|
|
148
|
+
* - For type 'url_reader' and HTTP/unknown URLs:
|
|
149
|
+
* URL_READER_HTTP_PROXY, URL_READER_HTTPS_PROXY, url_reader_http_proxy, url_reader_https_proxy,
|
|
150
|
+
* then HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy
|
|
151
|
+
* - For no specific type and HTTPS URLs:
|
|
152
|
+
* HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy
|
|
153
|
+
* - For no specific type and HTTP/unknown URLs:
|
|
154
|
+
* HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy
|
|
62
155
|
* - NO_PROXY / no_proxy: Comma-separated list of hosts to bypass proxy
|
|
63
156
|
*
|
|
64
157
|
* @param targetUrl - Optional target URL to check against NO_PROXY rules
|
|
158
|
+
* @param type - Optional proxy type ('search' or 'url_reader') for separate proxy configs
|
|
65
159
|
* @returns ProxyAgent dispatcher for fetch, or undefined if no proxy configured or bypassed
|
|
66
160
|
*/
|
|
67
|
-
export function createProxyAgent(targetUrl) {
|
|
68
|
-
const proxyUrl =
|
|
161
|
+
export function createProxyAgent(targetUrl, type) {
|
|
162
|
+
const proxyUrl = getProxyUrl(type, targetUrl);
|
|
69
163
|
if (!proxyUrl) {
|
|
70
164
|
return undefined;
|
|
71
165
|
}
|
package/dist/search.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function performWebSearch(
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function performWebSearch(mcpServer: McpServer, query: string, pageno?: number, time_range?: string, language?: string, safesearch?: number): Promise<string>;
|
package/dist/search.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createProxyAgent } from "./proxy.js";
|
|
1
|
+
import { createProxyAgent, ProxyType } from "./proxy.js";
|
|
2
2
|
import { logMessage } from "./logging.js";
|
|
3
3
|
import { createConfigurationError, createNetworkError, createServerError, createJSONError, createDataError, createNoResultsMessage } from "./error-handler.js";
|
|
4
|
-
export async function performWebSearch(
|
|
4
|
+
export async function performWebSearch(mcpServer, query, pageno = 1, time_range, language = "all", safesearch) {
|
|
5
5
|
const startTime = Date.now();
|
|
6
6
|
// Build detailed log message with all parameters
|
|
7
7
|
const searchParams = [
|
|
@@ -10,10 +10,10 @@ export async function performWebSearch(server, query, pageno = 1, time_range, la
|
|
|
10
10
|
time_range ? `time: ${time_range}` : null,
|
|
11
11
|
safesearch ? `safesearch: ${safesearch}` : null
|
|
12
12
|
].filter(Boolean).join(", ");
|
|
13
|
-
logMessage(
|
|
13
|
+
logMessage(mcpServer, "info", `Starting web search: "${query}" (${searchParams})`);
|
|
14
14
|
const searxngUrl = process.env.SEARXNG_URL;
|
|
15
15
|
if (!searxngUrl) {
|
|
16
|
-
logMessage(
|
|
16
|
+
logMessage(mcpServer, "error", "SEARXNG_URL not configured");
|
|
17
17
|
throw createConfigurationError("SEARXNG_URL not set. Set it to your SearXNG instance (e.g., http://localhost:8080 or https://search.example.com)");
|
|
18
18
|
}
|
|
19
19
|
// Validate that searxngUrl is a valid URL
|
|
@@ -44,7 +44,7 @@ export async function performWebSearch(server, query, pageno = 1, time_range, la
|
|
|
44
44
|
};
|
|
45
45
|
// Add proxy dispatcher if proxy is configured
|
|
46
46
|
// Node.js fetch uses 'dispatcher' option for proxy, not 'agent'
|
|
47
|
-
const proxyAgent = createProxyAgent(url.toString());
|
|
47
|
+
const proxyAgent = createProxyAgent(url.toString(), ProxyType.SEARCH);
|
|
48
48
|
if (proxyAgent) {
|
|
49
49
|
requestOptions.dispatcher = proxyAgent;
|
|
50
50
|
}
|
|
@@ -69,11 +69,11 @@ export async function performWebSearch(server, query, pageno = 1, time_range, la
|
|
|
69
69
|
// Fetch with enhanced error handling
|
|
70
70
|
let response;
|
|
71
71
|
try {
|
|
72
|
-
logMessage(
|
|
72
|
+
logMessage(mcpServer, "info", `Making request to: ${url.toString()}`);
|
|
73
73
|
response = await fetch(url.toString(), requestOptions);
|
|
74
74
|
}
|
|
75
75
|
catch (error) {
|
|
76
|
-
logMessage(
|
|
76
|
+
logMessage(mcpServer, "error", `Network error during search request: ${error.message}`, { query, url: url.toString() });
|
|
77
77
|
const context = {
|
|
78
78
|
url: url.toString(),
|
|
79
79
|
searxngUrl,
|
|
@@ -123,11 +123,11 @@ export async function performWebSearch(server, query, pageno = 1, time_range, la
|
|
|
123
123
|
score: result.score || 0,
|
|
124
124
|
}));
|
|
125
125
|
if (results.length === 0) {
|
|
126
|
-
logMessage(
|
|
126
|
+
logMessage(mcpServer, "info", `No results found for query: "${query}"`);
|
|
127
127
|
return createNoResultsMessage(query);
|
|
128
128
|
}
|
|
129
129
|
const duration = Date.now() - startTime;
|
|
130
|
-
logMessage(
|
|
130
|
+
logMessage(mcpServer, "info", `Search completed: "${query}" (${searchParams}) - ${results.length} results in ${duration}ms`);
|
|
131
131
|
return results
|
|
132
132
|
.map((r) => `Title: ${r.title}\nDescription: ${r.content}\nURL: ${r.url}\nRelevance Score: ${r.score.toFixed(3)}`)
|
|
133
133
|
.join("\n\n");
|
package/dist/url-reader.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
interface PaginationOptions {
|
|
3
3
|
startChar?: number;
|
|
4
4
|
maxLength?: number;
|
|
@@ -6,5 +6,5 @@ interface PaginationOptions {
|
|
|
6
6
|
paragraphRange?: string;
|
|
7
7
|
readHeadings?: boolean;
|
|
8
8
|
}
|
|
9
|
-
export declare function fetchAndConvertToMarkdown(
|
|
9
|
+
export declare function fetchAndConvertToMarkdown(mcpServer: McpServer, url: string, timeoutMs?: number, paginationOptions?: PaginationOptions): Promise<string>;
|
|
10
10
|
export {};
|
package/dist/url-reader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NodeHtmlMarkdown } from "node-html-markdown";
|
|
2
|
-
import { createProxyAgent } from "./proxy.js";
|
|
2
|
+
import { createProxyAgent, ProxyType } from "./proxy.js";
|
|
3
3
|
import { logMessage } from "./logging.js";
|
|
4
4
|
import { urlCache } from "./cache.js";
|
|
5
5
|
import { createURLFormatError, createNetworkError, createServerError, createContentError, createConversionError, createTimeoutError, createEmptyContentWarning, createUnexpectedError } from "./error-handler.js";
|
|
@@ -100,16 +100,16 @@ function applyPaginationOptions(markdownContent, options) {
|
|
|
100
100
|
}
|
|
101
101
|
return result;
|
|
102
102
|
}
|
|
103
|
-
export async function fetchAndConvertToMarkdown(
|
|
103
|
+
export async function fetchAndConvertToMarkdown(mcpServer, url, timeoutMs = 10000, paginationOptions = {}) {
|
|
104
104
|
const startTime = Date.now();
|
|
105
|
-
logMessage(
|
|
105
|
+
logMessage(mcpServer, "info", `Fetching URL: ${url}`);
|
|
106
106
|
// Check cache first
|
|
107
107
|
const cachedEntry = urlCache.get(url);
|
|
108
108
|
if (cachedEntry) {
|
|
109
|
-
logMessage(
|
|
109
|
+
logMessage(mcpServer, "info", `Using cached content for URL: ${url}`);
|
|
110
110
|
const result = applyPaginationOptions(cachedEntry.markdownContent, paginationOptions);
|
|
111
111
|
const duration = Date.now() - startTime;
|
|
112
|
-
logMessage(
|
|
112
|
+
logMessage(mcpServer, "info", `Processed cached URL: ${url} (${result.length} chars in ${duration}ms)`);
|
|
113
113
|
return result;
|
|
114
114
|
}
|
|
115
115
|
// Validate URL format
|
|
@@ -118,7 +118,7 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
|
|
|
118
118
|
parsedUrl = new URL(url);
|
|
119
119
|
}
|
|
120
120
|
catch (error) {
|
|
121
|
-
logMessage(
|
|
121
|
+
logMessage(mcpServer, "error", `Invalid URL format: ${url}`);
|
|
122
122
|
throw createURLFormatError(url);
|
|
123
123
|
}
|
|
124
124
|
// Create an AbortController instance
|
|
@@ -131,10 +131,18 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
|
|
|
131
131
|
};
|
|
132
132
|
// Add proxy dispatcher if proxy is configured
|
|
133
133
|
// Node.js fetch uses 'dispatcher' option for proxy, not 'agent'
|
|
134
|
-
const proxyAgent = createProxyAgent(url);
|
|
134
|
+
const proxyAgent = createProxyAgent(url, ProxyType.URL_READER);
|
|
135
135
|
if (proxyAgent) {
|
|
136
136
|
requestOptions.dispatcher = proxyAgent;
|
|
137
137
|
}
|
|
138
|
+
// Add User-Agent header if configured (URL_READER_USER_AGENT takes priority over USER_AGENT)
|
|
139
|
+
const userAgent = process.env.URL_READER_USER_AGENT || process.env.USER_AGENT;
|
|
140
|
+
if (userAgent) {
|
|
141
|
+
requestOptions.headers = {
|
|
142
|
+
...requestOptions.headers,
|
|
143
|
+
'User-Agent': userAgent
|
|
144
|
+
};
|
|
145
|
+
}
|
|
138
146
|
let response;
|
|
139
147
|
try {
|
|
140
148
|
// Fetch the URL with the abort signal
|
|
@@ -179,7 +187,7 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
|
|
|
179
187
|
throw createConversionError(error, url, htmlContent);
|
|
180
188
|
}
|
|
181
189
|
if (!markdownContent || markdownContent.trim().length === 0) {
|
|
182
|
-
logMessage(
|
|
190
|
+
logMessage(mcpServer, "warning", `Empty content after conversion: ${url}`);
|
|
183
191
|
// DON'T cache empty/failed conversions - return warning directly
|
|
184
192
|
return createEmptyContentWarning(url, htmlContent.length, htmlContent);
|
|
185
193
|
}
|
|
@@ -188,21 +196,21 @@ export async function fetchAndConvertToMarkdown(server, url, timeoutMs = 10000,
|
|
|
188
196
|
// Apply pagination options
|
|
189
197
|
const result = applyPaginationOptions(markdownContent, paginationOptions);
|
|
190
198
|
const duration = Date.now() - startTime;
|
|
191
|
-
logMessage(
|
|
199
|
+
logMessage(mcpServer, "info", `Successfully fetched and converted URL: ${url} (${result.length} chars in ${duration}ms)`);
|
|
192
200
|
return result;
|
|
193
201
|
}
|
|
194
202
|
catch (error) {
|
|
195
203
|
if (error.name === "AbortError") {
|
|
196
|
-
logMessage(
|
|
204
|
+
logMessage(mcpServer, "error", `Timeout fetching URL: ${url} (${timeoutMs}ms)`);
|
|
197
205
|
throw createTimeoutError(timeoutMs, url);
|
|
198
206
|
}
|
|
199
207
|
// Re-throw our enhanced errors
|
|
200
208
|
if (error.name === 'MCPSearXNGError') {
|
|
201
|
-
logMessage(
|
|
209
|
+
logMessage(mcpServer, "error", `Error fetching URL: ${url} - ${error.message}`);
|
|
202
210
|
throw error;
|
|
203
211
|
}
|
|
204
212
|
// Catch any unexpected errors
|
|
205
|
-
logMessage(
|
|
213
|
+
logMessage(mcpServer, "error", `Unexpected error fetching URL: ${url}`, error);
|
|
206
214
|
const context = { url };
|
|
207
215
|
throw createUnexpectedError(error, context);
|
|
208
216
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-searxng",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "MCP server for SearXNG integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ihor Sokoliuk (https://github.com/ihor-sokoliuk)",
|
|
@@ -36,29 +36,33 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc && shx chmod +x dist/*.js",
|
|
38
38
|
"watch": "tsc --watch",
|
|
39
|
-
"test": "SEARXNG_URL=https://test-searx.example.com tsx __tests__/run-all.ts",
|
|
40
|
-
"test:coverage": "SEARXNG_URL=https://test-searx.example.com c8 --reporter=text tsx __tests__/run-all.ts",
|
|
39
|
+
"test": "cross-env SEARXNG_URL=https://test-searx.example.com tsx __tests__/run-all.ts",
|
|
40
|
+
"test:coverage": "cross-env SEARXNG_URL=https://test-searx.example.com c8 --reporter=text tsx __tests__/run-all.ts",
|
|
41
41
|
"bootstrap": "npm install && npm run build",
|
|
42
42
|
"inspector": "DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector node dist/index.js",
|
|
43
|
+
"lint": "eslint src __tests__",
|
|
43
44
|
"postversion": "TAG=$(node scripts/update-version.js | tail -1) && git add src/index.ts && git commit --amend --no-edit && git tag -f $TAG"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@modelcontextprotocol/sdk": "1.
|
|
47
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
47
48
|
"@types/cors": "^2.8.19",
|
|
48
|
-
"@types/express": "^5.0.
|
|
49
|
-
"cors": "^2.8.
|
|
50
|
-
"express": "^5.1
|
|
51
|
-
"node-html-markdown": "^
|
|
52
|
-
"undici": "^
|
|
49
|
+
"@types/express": "^5.0.6",
|
|
50
|
+
"cors": "^2.8.6",
|
|
51
|
+
"express": "^5.2.1",
|
|
52
|
+
"node-html-markdown": "^2.0.0",
|
|
53
|
+
"undici": "^7.0.0"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"mcp-evals": "^1.0.18",
|
|
56
56
|
"@types/node": "^22.17.2",
|
|
57
|
-
"@types/supertest": "^
|
|
58
|
-
"
|
|
57
|
+
"@types/supertest": "^7.2.0",
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
59
|
+
"@typescript-eslint/parser": "^8.58.0",
|
|
60
|
+
"c8": "^11.0.0",
|
|
61
|
+
"cross-env": "^10.1.0",
|
|
62
|
+
"eslint": "^10.1.0",
|
|
59
63
|
"shx": "^0.4.0",
|
|
60
|
-
"supertest": "^7.
|
|
61
|
-
"tsx": "^4.
|
|
64
|
+
"supertest": "^7.2.2",
|
|
65
|
+
"tsx": "^4.21.0",
|
|
62
66
|
"typescript": "^5.8.3"
|
|
63
67
|
}
|
|
64
68
|
}
|