@kassol/mcp-searxng 1.0.3-custom.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,13 +2,12 @@
2
2
 
3
3
  An [MCP server](https://modelcontextprotocol.io/introduction) that integrates the [SearXNG](https://docs.searxng.org) API, giving AI assistants web search capabilities.
4
4
 
5
- This fork is published as `@kassol/mcp-searxng` and includes support for custom outgoing headers via `SEARXNG_HEADERS` and `URL_READER_HEADERS`.
5
+ This fork is published as `@kassol/mcp-searxng` and includes support for custom outgoing headers via `SEARXNG_HEADERS`, `URL_READER_HEADERS`, and their base64 variants.
6
6
 
7
- [![https://nodei.co/npm/mcp-searxng.png?downloads=true&downloadRank=true&stars=true](https://nodei.co/npm/mcp-searxng.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/mcp-searxng)
8
-
9
- [![https://badgen.net/docker/pulls/isokoliuk/mcp-searxng](https://badgen.net/docker/pulls/isokoliuk/mcp-searxng)](https://hub.docker.com/r/isokoliuk/mcp-searxng)
10
-
11
- <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>
7
+ [![npm version](https://img.shields.io/npm/v/%40kassol%2Fmcp-searxng?label=npm)](https://www.npmjs.com/package/@kassol/mcp-searxng)
8
+ [![npm downloads](https://img.shields.io/npm/dm/%40kassol%2Fmcp-searxng)](https://www.npmjs.com/package/@kassol/mcp-searxng)
9
+ [![license](https://img.shields.io/npm/l/%40kassol%2Fmcp-searxng)](LICENSE)
10
+ [![GitHub repo](https://img.shields.io/badge/github-kassol%2Fmcp--searxng-24292f?logo=github)](https://github.com/kassol/mcp-searxng)
12
11
 
13
12
  ## Quick Start
14
13
 
@@ -102,12 +101,10 @@ npm install -g @kassol/mcp-searxng
102
101
  </details>
103
102
 
104
103
  <details>
105
- <summary>Docker</summary>
106
-
107
- **Pre-built image:**
104
+ <summary>Docker (local build)</summary>
108
105
 
109
106
  ```bash
110
- docker pull isokoliuk/mcp-searxng:latest
107
+ docker build -t mcp-searxng:latest -f Dockerfile .
111
108
  ```
112
109
 
113
110
  ```json
@@ -118,7 +115,7 @@ docker pull isokoliuk/mcp-searxng:latest
118
115
  "args": [
119
116
  "run", "-i", "--rm",
120
117
  "-e", "SEARXNG_URL",
121
- "isokoliuk/mcp-searxng:latest"
118
+ "mcp-searxng:latest"
122
119
  ],
123
120
  "env": {
124
121
  "SEARXNG_URL": "YOUR_SEARXNG_INSTANCE_URL"
@@ -130,14 +127,6 @@ docker pull isokoliuk/mcp-searxng:latest
130
127
 
131
128
  To pass additional env vars, add `-e VAR_NAME` to `args` and the variable to `env`.
132
129
 
133
- **Build locally:**
134
-
135
- ```bash
136
- docker build -t mcp-searxng:latest -f Dockerfile .
137
- ```
138
-
139
- Use the same config above, replacing `isokoliuk/mcp-searxng:latest` with `mcp-searxng:latest`.
140
-
141
130
  </details>
142
131
 
143
132
  <details>
@@ -148,7 +137,10 @@ Use the same config above, replacing `isokoliuk/mcp-searxng:latest` with `mcp-se
148
137
  ```yaml
149
138
  services:
150
139
  mcp-searxng:
151
- image: isokoliuk/mcp-searxng:latest
140
+ build:
141
+ context: .
142
+ dockerfile: Dockerfile
143
+ image: mcp-searxng:latest
152
144
  stdin_open: true
153
145
  environment:
154
146
  - SEARXNG_URL=YOUR_SEARXNG_INSTANCE_URL
@@ -161,8 +153,8 @@ MCP client config:
161
153
  {
162
154
  "mcpServers": {
163
155
  "searxng": {
164
- "command": "docker-compose",
165
- "args": ["run", "--rm", "mcp-searxng"]
156
+ "command": "docker",
157
+ "args": ["compose", "run", "--rm", "mcp-searxng"]
166
158
  }
167
159
  }
168
160
  }
@@ -204,7 +196,25 @@ curl http://localhost:3000/health
204
196
 
205
197
  Set `SEARXNG_URL` to your SearXNG instance URL. All other variables are optional.
206
198
 
207
- Protected SearXNG instances can receive extra search request headers through `SEARXNG_HEADERS`:
199
+ Protected SearXNG instances can receive extra search request headers through `SEARXNG_HEADERS_BASE64`. This is the recommended format for ChatWise and other MCP clients that treat environment variables as plain key-value fields.
200
+
201
+ Generate the value directly from existing Cloudflare Access environment variables:
202
+
203
+ ```bash
204
+ export CF_ACCESS_CLIENT_ID='your-client-id.access'
205
+ export CF_ACCESS_CLIENT_SECRET='your-client-secret'
206
+
207
+ node -e 'console.log(Buffer.from(JSON.stringify({"CF-Access-Client-Id":process.env.CF_ACCESS_CLIENT_ID,"CF-Access-Client-Secret":process.env.CF_ACCESS_CLIENT_SECRET})).toString("base64"))'
208
+ ```
209
+
210
+ Verify the generated value:
211
+
212
+ ```bash
213
+ export SEARXNG_HEADERS_BASE64='paste-generated-value-here'
214
+ node -e 'console.log(Buffer.from(process.env.SEARXNG_HEADERS_BASE64,"base64").toString("utf8"))'
215
+ ```
216
+
217
+ MCP client configuration:
208
218
 
209
219
  ```json
210
220
  {
@@ -214,13 +224,23 @@ Protected SearXNG instances can receive extra search request headers through `SE
214
224
  "args": ["-y", "@kassol/mcp-searxng"],
215
225
  "env": {
216
226
  "SEARXNG_URL": "https://search.example.com",
217
- "SEARXNG_HEADERS": "{\"CF-Access-Client-Id\":\"your-client-id.access\",\"CF-Access-Client-Secret\":\"your-client-secret\"}"
227
+ "SEARXNG_HEADERS_BASE64": "eyJDRi1BY2Nlc3MtQ2xpZW50LUlkIjoieW91ci1jbGllbnQtaWQuYWNjZXNzIiwiQ0YtQWNjZXNzLUNsaWVudC1TZWNyZXQiOiJ5b3VyLWNsaWVudC1zZWNyZXQifQ=="
218
228
  }
219
229
  }
220
230
  }
221
231
  }
222
232
  ```
223
233
 
234
+ ChatWise environment variables:
235
+
236
+ ```text
237
+ SEARXNG_URL=https://search.example.com
238
+ USER_AGENT=Mozilla/5.0
239
+ SEARXNG_HEADERS_BASE64=paste-generated-value-here
240
+ ```
241
+
242
+ `SEARXNG_HEADERS_BASE64` applies only to `searxng_web_search` requests. Use `URL_READER_HEADERS_BASE64` for headers that should be sent by `web_url_read`.
243
+
224
244
  Full environment variable reference: [CONFIGURATION.md](CONFIGURATION.md)
225
245
 
226
246
  ## Troubleshooting
package/dist/headers.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export type HeaderRecord = Record<string, string>;
2
2
  export declare function parseHeadersFromEnv(envVarName: string): HeaderRecord;
3
+ export declare function parseHeadersFromBase64Env(envVarName: string): HeaderRecord;
4
+ export declare function getSearxngHeadersFromEnv(): HeaderRecord;
5
+ export declare function getUrlReaderHeadersFromEnv(): HeaderRecord;
3
6
  export declare function mergeHeaders(headers: HeadersInit | undefined, additionalHeaders: HeaderRecord): HeaderRecord;
package/dist/headers.js CHANGED
@@ -47,6 +47,23 @@ export function parseHeadersFromEnv(envVarName) {
47
47
  if (!rawHeaders) {
48
48
  return createHeaderRecord();
49
49
  }
50
+ return parseHeadersJson(rawHeaders, envVarName);
51
+ }
52
+ export function parseHeadersFromBase64Env(envVarName) {
53
+ const rawHeaders = process.env[envVarName];
54
+ if (!rawHeaders) {
55
+ return createHeaderRecord();
56
+ }
57
+ let decodedHeaders;
58
+ try {
59
+ decodedHeaders = Buffer.from(rawHeaders, "base64").toString("utf8");
60
+ }
61
+ catch {
62
+ throw createConfigurationError(`${envVarName} must be valid base64`);
63
+ }
64
+ return parseHeadersJson(decodedHeaders, envVarName);
65
+ }
66
+ function parseHeadersJson(rawHeaders, envVarName) {
50
67
  let parsedHeaders;
51
68
  try {
52
69
  parsedHeaders = JSON.parse(rawHeaders);
@@ -68,6 +85,15 @@ export function parseHeadersFromEnv(envVarName) {
68
85
  }
69
86
  return headers;
70
87
  }
88
+ export function getSearxngHeadersFromEnv() {
89
+ return getHeadersFromEnv("SEARXNG_HEADERS", "SEARXNG_HEADERS_BASE64");
90
+ }
91
+ export function getUrlReaderHeadersFromEnv() {
92
+ return getHeadersFromEnv("URL_READER_HEADERS", "URL_READER_HEADERS_BASE64");
93
+ }
94
+ function getHeadersFromEnv(jsonEnvVarName, base64EnvVarName) {
95
+ return mergeHeaders(parseHeadersFromEnv(jsonEnvVarName), parseHeadersFromBase64Env(base64EnvVarName));
96
+ }
71
97
  export function mergeHeaders(headers, additionalHeaders) {
72
98
  const mergedHeaders = normalizeHeaders(headers);
73
99
  for (const [name, value] of Object.entries(additionalHeaders)) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- declare const packageVersion = "1.0.3-custom.0";
3
+ declare const packageVersion = "1.1.0";
4
4
  export { packageVersion };
5
5
  export declare function isWebUrlReadArgs(args: unknown): args is {
6
6
  url: string;
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { fetchAndConvertToMarkdown } from "./url-reader.js";
10
10
  import { createConfigResource, createHelpResource } from "./resources.js";
11
11
  import { createHttpServer } from "./http-server.js";
12
12
  // Use a static version string that will be updated by the version script
13
- const packageVersion = "1.0.3-custom.0";
13
+ const packageVersion = "1.1.0";
14
14
  // Export the version for use in other modules
15
15
  export { packageVersion };
16
16
  // Type guard for URL reading args
package/dist/resources.js CHANGED
@@ -15,8 +15,8 @@ export function createConfigResource() {
15
15
  ? { searxngUrl: process.env.SEARXNG_URL || "(not configured)" }
16
16
  : { searxngUrlConfigured: !!process.env.SEARXNG_URL }),
17
17
  hasAuth: !!(process.env.AUTH_USERNAME && process.env.AUTH_PASSWORD),
18
- hasSearxngHeaders: !!process.env.SEARXNG_HEADERS,
19
- hasUrlReaderHeaders: !!process.env.URL_READER_HEADERS,
18
+ hasSearxngHeaders: !!(process.env.SEARXNG_HEADERS || process.env.SEARXNG_HEADERS_BASE64),
19
+ hasUrlReaderHeaders: !!(process.env.URL_READER_HEADERS || process.env.URL_READER_HEADERS_BASE64),
20
20
  hasProxy: !!(process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy),
21
21
  hasNoProxy: !!(process.env.NO_PROXY || process.env.no_proxy),
22
22
  nodeVersion: process.version,
@@ -65,7 +65,9 @@ Reads and converts web page content to Markdown format.
65
65
  - \`USER_AGENT\`: Global User-Agent header for outgoing requests
66
66
  - \`URL_READER_USER_AGENT\`: User-Agent for \`web_url_read\`, overrides \`USER_AGENT\`
67
67
  - \`SEARXNG_HEADERS\`: Extra JSON headers for SearXNG search requests
68
+ - \`SEARXNG_HEADERS_BASE64\`: Base64-encoded JSON headers for SearXNG search requests
68
69
  - \`URL_READER_HEADERS\`: Extra JSON headers for URL read requests
70
+ - \`URL_READER_HEADERS_BASE64\`: Base64-encoded JSON headers for URL read requests
69
71
  - \`HTTP_PROXY\` / \`HTTPS_PROXY\`: Proxy server configuration
70
72
  - \`NO_PROXY\` / \`no_proxy\`: Comma-separated list of hosts to bypass proxy
71
73
  - \`MCP_HTTP_PORT\`: Enable HTTP transport on specified port
package/dist/search.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createProxyAgent, createDefaultAgent, ProxyType } from "./proxy.js";
2
2
  import { logMessage } from "./logging.js";
3
- import { mergeHeaders, parseHeadersFromEnv } from "./headers.js";
3
+ import { getSearxngHeadersFromEnv, mergeHeaders } from "./headers.js";
4
4
  import { MCPSearXNGError, validateEnvironment, createNetworkError, createServerError, createJSONError, createDataError, createNoResultsMessage } from "./error-handler.js";
5
5
  export async function performWebSearch(mcpServer, query, pageno = 1, time_range, language = "all", safesearch) {
6
6
  const startTime = Date.now();
@@ -61,7 +61,7 @@ export async function performWebSearch(mcpServer, query, pageno = 1, time_range,
61
61
  'User-Agent': userAgent
62
62
  };
63
63
  }
64
- const additionalHeaders = parseHeadersFromEnv("SEARXNG_HEADERS");
64
+ const additionalHeaders = getSearxngHeadersFromEnv();
65
65
  if (Object.keys(additionalHeaders).length > 0) {
66
66
  requestOptions.headers = mergeHeaders(requestOptions.headers, additionalHeaders);
67
67
  }
@@ -4,7 +4,7 @@ import { createProxyAgent, createDefaultAgent, ProxyType } from "./proxy.js";
4
4
  import { logMessage } from "./logging.js";
5
5
  import { urlCache } from "./cache.js";
6
6
  import { getHttpSecurityConfig } from "./http-security.js";
7
- import { mergeHeaders, parseHeadersFromEnv } from "./headers.js";
7
+ import { getUrlReaderHeadersFromEnv, mergeHeaders } from "./headers.js";
8
8
  import { createURLFormatError, createURLSecurityPolicyError, createNetworkError, createServerError, createContentError, createConversionError, createTimeoutError, createEmptyContentWarning, createUnexpectedError } from "./error-handler.js";
9
9
  function isPrivateHostname(hostname) {
10
10
  const lower = hostname.toLowerCase().replace(/\.+$/, "");
@@ -194,7 +194,7 @@ export async function fetchAndConvertToMarkdown(mcpServer, url, timeoutMs = 1000
194
194
  'User-Agent': userAgent
195
195
  };
196
196
  }
197
- const additionalHeaders = parseHeadersFromEnv("URL_READER_HEADERS");
197
+ const additionalHeaders = getUrlReaderHeadersFromEnv();
198
198
  if (Object.keys(additionalHeaders).length > 0) {
199
199
  requestOptions.headers = mergeHeaders(requestOptions.headers, additionalHeaders);
200
200
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kassol/mcp-searxng",
3
- "version": "1.0.3-custom.0",
3
+ "version": "1.1.0",
4
4
  "mcpName": "io.github.kassol/mcp-searxng",
5
5
  "description": "MCP server for SearXNG integration",
6
6
  "license": "MIT",
@@ -45,6 +45,12 @@
45
45
  "bootstrap": "npm install && npm run build",
46
46
  "inspector": "DANGEROUSLY_OMIT_AUTH=true npx @modelcontextprotocol/inspector node dist/index.js",
47
47
  "lint": "eslint src __tests__",
48
+ "sync-version": "node scripts/update-version.js",
49
+ "release:check": "npm run build && npm run lint && npm test && npm pack --dry-run",
50
+ "release:dry-run": "npm publish --dry-run --access public --tag latest",
51
+ "release:patch": "npm version patch",
52
+ "release:minor": "npm version minor",
53
+ "release:major": "npm version major",
48
54
  "postversion": "TAG=$(node scripts/update-version.js | tail -1) && git add src/index.ts .mcp/server.json && git commit --amend --no-edit && git tag -f $TAG"
49
55
  },
50
56
  "dependencies": {