mcp-searxng 1.4.0 → 1.5.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
@@ -40,6 +40,10 @@ Replace `YOUR_SEARXNG_INSTANCE_URL` with the URL of your SearXNG instance (e.g.
40
40
  ## Features
41
41
 
42
42
  - **Web Search**: General queries, news, articles, with pagination.
43
+ - **Structured Search Output**: Choose formatted text or raw SearXNG-shaped JSON with `response_format`.
44
+ - **Direct Answers & Metadata**: Text results surface SearXNG answers, corrections, suggestions, and infoboxes before result lists.
45
+ - **Search Suggestions**: Query autocomplete via SearXNG's `/autocompleter` endpoint.
46
+ - **Instance Capability Discovery**: Inspect configured categories, engines, defaults, locales, and plugins from `/config`.
43
47
  - **URL Content Reading**: Advanced content extraction with pagination, section filtering, and heading extraction.
44
48
  - **Intelligent Caching**: URL content is cached with TTL (Time-To-Live) to improve performance and reduce redundant requests.
45
49
  - **Pagination**: Control which page of results to retrieve.
@@ -88,6 +92,21 @@ AI Assistant (e.g. Claude)
88
92
  - `min_score` (number, optional): Minimum relevance score from 0.0 to 1.0. Results below this score are filtered out.
89
93
  - `num_results` (number, optional): Maximum number of results to return, from 1 to 20. `SEARXNG_MAX_RESULTS` applies as an operator ceiling.
90
94
  - `categories` (string, optional): Comma-separated SearXNG categories (e.g. `"news"`, `"it,science"`). Supported values: `general`, `news`, `images`, `videos`, `it`, `science`, `files`, `social media`. Default: SearXNG instance default (usually `general`).
95
+ - `response_format` (string, optional): Response format, either `"text"` for formatted agent-readable output or `"json"` for raw SearXNG JSON with filtered/sliced `results`. (default: `"text"`)
96
+
97
+ - **searxng_search_suggestions**
98
+ - Get autocomplete suggestions for refining search queries
99
+ - Inputs:
100
+ - `query` (string): Partial or complete query to autocomplete.
101
+ - `language` (string, optional): Language code for suggestions (e.g., "en", "fr", "de") or "all" (default: "all")
102
+
103
+ - **searxng_instance_info**
104
+ - Discover categories, engines, defaults, locales, and plugins exposed by the configured SearXNG instance
105
+ - Inputs:
106
+ - `includeEngines` (boolean, optional): Include enabled engine names in the response. (default: false)
107
+ - `includeDisabled` (boolean, optional): Include disabled engine names when `includeEngines` is true. (default: false)
108
+ - `category` (string, optional): Filter categories and engines to a single category name.
109
+ - `refresh` (boolean, optional): Bypass the process cache and fetch fresh `/config` data. (default: false)
91
110
 
92
111
  - **web_url_read**
93
112
  - Read and convert the content from a URL to markdown with advanced content extraction options
package/dist/index.js CHANGED
@@ -2,9 +2,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
4
4
  // Import modularized functionality
5
- import { WEB_SEARCH_TOOL, READ_URL_TOOL, LITE_WEB_SEARCH_TOOL, LITE_READ_URL_TOOL, isSearXNGWebSearchArgs } from "./types.js";
5
+ import { WEB_SEARCH_TOOL, SUGGESTIONS_TOOL, INSTANCE_INFO_TOOL, READ_URL_TOOL, LITE_WEB_SEARCH_TOOL, LITE_SUGGESTIONS_TOOL, LITE_INSTANCE_INFO_TOOL, LITE_READ_URL_TOOL, isSearXNGWebSearchArgs, isSearXNGSearchSuggestionsArgs, isSearXNGInstanceInfoArgs, } from "./types.js";
6
6
  import { logMessage, setLogLevel, getCurrentLogLevel } from "./logging.js";
7
7
  import { performWebSearch } from "./search.js";
8
+ import { performSearchSuggestions } from "./suggestions.js";
9
+ import { fetchInstanceInfo } from "./instance-info.js";
8
10
  import { fetchAndConvertToMarkdown } from "./url-reader.js";
9
11
  import { createConfigResource, createHelpResource } from "./resources.js";
10
12
  import { createHttpServer, resolveBindHost } from "./http-server.js";
@@ -83,12 +85,14 @@ export function createMcpServer() {
83
85
  const server = mcpServer.server;
84
86
  const useLiteTools = process.env.SEARXNG_LITE_TOOLS === "true";
85
87
  const searchTool = useLiteTools ? LITE_WEB_SEARCH_TOOL : WEB_SEARCH_TOOL;
88
+ const suggestionsTool = useLiteTools ? LITE_SUGGESTIONS_TOOL : SUGGESTIONS_TOOL;
89
+ const instanceInfoTool = useLiteTools ? LITE_INSTANCE_INFO_TOOL : INSTANCE_INFO_TOOL;
86
90
  const readUrlTool = useLiteTools ? LITE_READ_URL_TOOL : READ_URL_TOOL;
87
91
  // List tools handler
88
92
  server.setRequestHandler(ListToolsRequestSchema, async () => {
89
93
  logMessage(mcpServer, "debug", "Handling list_tools request");
90
94
  return {
91
- tools: [searchTool, readUrlTool],
95
+ tools: [searchTool, suggestionsTool, instanceInfoTool, readUrlTool],
92
96
  };
93
97
  });
94
98
  // Call tool handler
@@ -100,7 +104,35 @@ export function createMcpServer() {
100
104
  if (!isSearXNGWebSearchArgs(args)) {
101
105
  throw new Error("Invalid arguments for web search");
102
106
  }
103
- const result = await performWebSearch(mcpServer, args.query, args.pageno, args.time_range, args.language, args.safesearch, args.min_score, args.num_results, args.categories);
107
+ const result = await performWebSearch(mcpServer, args.query, args.pageno, args.time_range, args.language, args.safesearch, args.min_score, args.num_results, args.categories, args.response_format);
108
+ return {
109
+ content: [
110
+ {
111
+ type: "text",
112
+ text: result,
113
+ },
114
+ ],
115
+ };
116
+ }
117
+ else if (name === "searxng_search_suggestions") {
118
+ if (!isSearXNGSearchSuggestionsArgs(args)) {
119
+ throw new Error("Invalid arguments for search suggestions");
120
+ }
121
+ const suggestions = await performSearchSuggestions(mcpServer, args.query, args.language);
122
+ return {
123
+ content: [
124
+ {
125
+ type: "text",
126
+ text: JSON.stringify({ query: args.query, suggestions }, null, 2),
127
+ },
128
+ ],
129
+ };
130
+ }
131
+ else if (name === "searxng_instance_info") {
132
+ if (!isSearXNGInstanceInfoArgs(args)) {
133
+ throw new Error("Invalid arguments for instance info");
134
+ }
135
+ const result = await fetchInstanceInfo(mcpServer, args.includeEngines, args.includeDisabled, args.category, args.refresh);
104
136
  return {
105
137
  content: [
106
138
  {
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function clearInstanceInfoCacheForTests(): void;
3
+ export declare function fetchInstanceInfo(mcpServer: McpServer, includeEngines?: boolean, includeDisabled?: boolean, category?: string, refresh?: boolean): Promise<string>;
@@ -0,0 +1,113 @@
1
+ import { logMessage } from "./logging.js";
2
+ import { createDefaultAgent, createProxyAgent, ProxyType } from "./proxy.js";
3
+ let cachedConfig = null;
4
+ let cachedBaseUrl = null;
5
+ function unavailable(message, status) {
6
+ return JSON.stringify({
7
+ available: false,
8
+ message,
9
+ ...(status !== undefined ? { status } : {}),
10
+ }, null, 2);
11
+ }
12
+ function namesFromCategories(config) {
13
+ if (!config.categories || typeof config.categories !== "object") {
14
+ return [];
15
+ }
16
+ return Object.keys(config.categories).sort();
17
+ }
18
+ function engineCategories(engine) {
19
+ if (Array.isArray(engine.categories)) {
20
+ return engine.categories;
21
+ }
22
+ if (typeof engine.category === "string") {
23
+ return [engine.category];
24
+ }
25
+ return [];
26
+ }
27
+ function collectEngines(config, includeDisabled, category) {
28
+ const enabled = new Set();
29
+ const disabled = new Set();
30
+ if (Array.isArray(config.engines)) {
31
+ for (const engine of config.engines) {
32
+ if (!engine || typeof engine.name !== "string") {
33
+ continue;
34
+ }
35
+ const categories = engineCategories(engine);
36
+ if (category && !categories.includes(category)) {
37
+ continue;
38
+ }
39
+ if (engine.disabled) {
40
+ if (includeDisabled) {
41
+ disabled.add(engine.name);
42
+ }
43
+ }
44
+ else {
45
+ enabled.add(engine.name);
46
+ }
47
+ }
48
+ }
49
+ return {
50
+ enabled: [...enabled].sort(),
51
+ ...(includeDisabled ? { disabled: [...disabled].sort() } : {}),
52
+ };
53
+ }
54
+ function formatInstanceInfo(config, includeEngines, includeDisabled, category) {
55
+ const categories = category
56
+ ? namesFromCategories(config).filter((name) => name === category)
57
+ : namesFromCategories(config);
58
+ const payload = {
59
+ available: true,
60
+ categories,
61
+ defaults: {
62
+ safesearch: config.search?.safe_search ?? config.default_safe_search,
63
+ locale: config.default_locale,
64
+ language: config.default_language,
65
+ theme: config.default_theme,
66
+ },
67
+ locales: config.locales,
68
+ plugins: config.plugins ?? [],
69
+ };
70
+ if (includeEngines) {
71
+ payload.engines = collectEngines(config, includeDisabled, category);
72
+ }
73
+ return JSON.stringify(payload, null, 2);
74
+ }
75
+ export function clearInstanceInfoCacheForTests() {
76
+ cachedConfig = null;
77
+ cachedBaseUrl = null;
78
+ }
79
+ export async function fetchInstanceInfo(mcpServer, includeEngines = false, includeDisabled = false, category, refresh = false) {
80
+ const base = process.env.SEARXNG_URL;
81
+ if (!base) {
82
+ return unavailable("SEARXNG_URL is not configured; cannot fetch SearXNG /config.");
83
+ }
84
+ if (refresh) {
85
+ cachedConfig = null;
86
+ }
87
+ if (cachedConfig && cachedBaseUrl === base) {
88
+ return formatInstanceInfo(cachedConfig, includeEngines, includeDisabled, category);
89
+ }
90
+ const parsedBase = new URL(base.endsWith("/") ? base : `${base}/`);
91
+ const url = new URL("config", parsedBase);
92
+ try {
93
+ const requestOptions = {
94
+ signal: AbortSignal.timeout(5000),
95
+ };
96
+ const proxyAgent = createProxyAgent(url.toString(), ProxyType.SEARCH);
97
+ const dispatcher = proxyAgent ?? createDefaultAgent();
98
+ if (dispatcher) {
99
+ requestOptions.dispatcher = dispatcher;
100
+ }
101
+ const response = await fetch(url.toString(), requestOptions);
102
+ if (!response.ok) {
103
+ return unavailable(`SearXNG /config is unavailable: HTTP ${response.status} ${response.statusText}`, response.status);
104
+ }
105
+ cachedConfig = await response.json();
106
+ cachedBaseUrl = base;
107
+ return formatInstanceInfo(cachedConfig, includeEngines, includeDisabled, category);
108
+ }
109
+ catch (error) {
110
+ logMessage(mcpServer, "warning", `SearXNG /config fetch failed: ${error instanceof Error ? error.message : String(error)}`);
111
+ return unavailable("SearXNG /config is unavailable; instance capability discovery could not complete.");
112
+ }
113
+ }
package/dist/resources.js CHANGED
@@ -21,7 +21,12 @@ export function createConfigResource() {
21
21
  currentLogLevel: getCurrentLogLevel()
22
22
  },
23
23
  capabilities: {
24
- tools: ["searxng_web_search", "web_url_read"],
24
+ tools: [
25
+ "searxng_web_search",
26
+ "searxng_search_suggestions",
27
+ "searxng_instance_info",
28
+ "web_url_read"
29
+ ],
25
30
  logging: true,
26
31
  resources: true,
27
32
  transports: process.env.MCP_HTTP_PORT ? ["stdio", "http"] : ["stdio"]
@@ -33,7 +38,7 @@ export function createHelpResource() {
33
38
  return `# SearXNG MCP Server Help
34
39
 
35
40
  ## Overview
36
- This is a Model Context Protocol (MCP) server that provides web search capabilities through SearXNG and URL content reading functionality.
41
+ This is a Model Context Protocol (MCP) server that provides web search, autocomplete suggestions, instance capability discovery, and URL content reading through SearXNG.
37
42
 
38
43
  ## Available Tools
39
44
 
@@ -47,12 +52,38 @@ Performs web searches using the configured SearXNG instance.
47
52
  - \`language\` (optional): Language code like "en", "fr", "de" (default: "all")
48
53
  - \`safesearch\` (optional): Safe search level - 0 (none), 1 (moderate), 2 (strict)
49
54
  - \`min_score\` (optional): Minimum relevance score from 0.0 to 1.0
55
+ - \`num_results\` (optional): Maximum result count from 1 to 20
56
+ - \`categories\` (optional): Comma-separated SearXNG categories such as "news" or "it,science"
57
+ - \`response_format\` (optional): "text" for formatted output or "json" for raw SearXNG-shaped JSON
50
58
 
51
- ### 2. web_url_read
59
+ Text output can include metadata sections for direct answers, spelling corrections, suggestions, and infoboxes before the result list. JSON output preserves the SearXNG response shape with filtered and sliced \`results\`.
60
+
61
+ ### 2. searxng_search_suggestions
62
+ Returns autocomplete suggestions from the configured SearXNG instance.
63
+
64
+ **Parameters:**
65
+ - \`query\` (required): Partial or complete query to autocomplete
66
+ - \`language\` (optional): Language code like "en", "fr", "de" or "all" (default: "all")
67
+
68
+ ### 3. searxng_instance_info
69
+ Discovers categories, engines, defaults, locales, and plugins exposed by the configured SearXNG instance.
70
+
71
+ **Parameters:**
72
+ - \`includeEngines\` (optional): Include enabled engine names
73
+ - \`includeDisabled\` (optional): Include disabled engine names when \`includeEngines\` is true
74
+ - \`category\` (optional): Filter categories and engines to one category
75
+ - \`refresh\` (optional): Bypass the process cache and fetch fresh \`/config\` data
76
+
77
+ ### 4. web_url_read
52
78
  Reads and converts web page content to Markdown format.
53
79
 
54
80
  **Parameters:**
55
81
  - \`url\` (required): The URL to fetch and convert
82
+ - \`startChar\` (optional): Starting character position
83
+ - \`maxLength\` (optional): Maximum number of characters to return
84
+ - \`section\` (optional): Extract content under a heading
85
+ - \`paragraphRange\` (optional): Return a paragraph range such as "1-5" or "10-"
86
+ - \`readHeadings\` (optional): Return only headings
56
87
 
57
88
  ## Configuration
58
89
 
@@ -98,6 +129,18 @@ Tool: web_url_read
98
129
  Args: {"url": "https://example.com/article"}
99
130
  \`\`\`
100
131
 
132
+ ### Get query suggestions
133
+ \`\`\`
134
+ Tool: searxng_search_suggestions
135
+ Args: {"query": "typescr"}
136
+ \`\`\`
137
+
138
+ ### Discover instance capabilities
139
+ \`\`\`
140
+ Tool: searxng_instance_info
141
+ Args: {"includeEngines": true}
142
+ \`\`\`
143
+
101
144
  ## Troubleshooting
102
145
 
103
146
  1. **"SEARXNG_URL not set"**: Configure the SEARXNG_URL environment variable
package/dist/search.d.ts CHANGED
@@ -1,2 +1,2 @@
1
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, min_score?: number, num_results?: number, categories?: string): Promise<string>;
2
+ export declare function performWebSearch(mcpServer: McpServer, query: string, pageno?: number, time_range?: string, language?: string, safesearch?: number, min_score?: number, num_results?: number, categories?: string, response_format?: "text" | "json"): Promise<string>;
package/dist/search.js CHANGED
@@ -31,6 +31,37 @@ function truncateResultContent(content, maxResultChars) {
31
31
  }
32
32
  return `${content.slice(0, maxResultChars)}…`;
33
33
  }
34
+ function hasItems(items) {
35
+ return Array.isArray(items) && items.length > 0;
36
+ }
37
+ function formatSearchMetadata(data) {
38
+ const sections = [];
39
+ if (hasItems(data.answers)) {
40
+ sections.push(data.answers.map((answer) => `Direct answer: ${answer}`).join("\n"));
41
+ }
42
+ if (hasItems(data.corrections)) {
43
+ sections.push(data.corrections.map((correction) => `Spelling correction: did you mean "${correction}"?`).join("\n"));
44
+ }
45
+ if (hasItems(data.suggestions)) {
46
+ sections.push(`Suggestions: ${data.suggestions.join(", ")}`);
47
+ }
48
+ if (hasItems(data.infoboxes)) {
49
+ const infoboxText = data.infoboxes
50
+ .map((infobox) => {
51
+ const lines = [`Infobox: ${infobox.infobox}`];
52
+ if (infobox.content) {
53
+ lines.push(infobox.content);
54
+ }
55
+ if (hasItems(infobox.urls)) {
56
+ lines.push(...infobox.urls.map((entry) => `${entry.title}: ${entry.url}`));
57
+ }
58
+ return lines.join("\n");
59
+ })
60
+ .join("\n\n");
61
+ sections.push(infoboxText);
62
+ }
63
+ return sections.join("\n\n");
64
+ }
34
65
  function getDefaultLanguage() {
35
66
  return process.env.SEARXNG_DEFAULT_LANGUAGE ?? "all";
36
67
  }
@@ -46,7 +77,7 @@ function getDefaultSafesearch(mcpServer) {
46
77
  }
47
78
  return parsed;
48
79
  }
49
- export async function performWebSearch(mcpServer, query, pageno = 1, time_range, language, safesearch, min_score, num_results, categories) {
80
+ export async function performWebSearch(mcpServer, query, pageno = 1, time_range, language, safesearch, min_score, num_results, categories, response_format = "text") {
50
81
  const startTime = Date.now();
51
82
  const operatorMax = getOperatorMaxResults(mcpServer);
52
83
  const effectiveMax = operatorMax !== undefined
@@ -177,16 +208,14 @@ export async function performWebSearch(mcpServer, query, pageno = 1, time_range,
177
208
  throw createDataError(data, context);
178
209
  }
179
210
  const results = data.results
180
- .map((result) => ({
181
- title: result.title || "",
182
- content: result.content || "",
183
- url: result.url || "",
184
- score: result.score || 0,
185
- }))
186
- .filter((result) => min_score === undefined || result.score >= min_score);
211
+ .filter((result) => min_score === undefined || (result.score || 0) >= min_score);
187
212
  const slicedResults = effectiveMax !== undefined
188
213
  ? results.slice(0, effectiveMax)
189
214
  : results;
215
+ if (response_format === "json") {
216
+ return JSON.stringify({ ...data, results: slicedResults }, null, 2);
217
+ }
218
+ const metadata = formatSearchMetadata(data);
190
219
  if (slicedResults.length === 0) {
191
220
  const appliedFilters = [
192
221
  min_score === undefined ? null : `min_score=${min_score}`,
@@ -194,11 +223,16 @@ export async function performWebSearch(mcpServer, query, pageno = 1, time_range,
194
223
  ].filter(Boolean).join(" ");
195
224
  const filterNote = appliedFilters ? ` after applying ${appliedFilters}` : "";
196
225
  logMessage(mcpServer, "info", `No results found for query: "${query}"${filterNote}`);
197
- return createNoResultsMessage(query);
226
+ const noResultsMessage = createNoResultsMessage(query);
227
+ return metadata ? `${metadata}\n\n---\n\n${noResultsMessage}` : noResultsMessage;
198
228
  }
199
229
  const duration = Date.now() - startTime;
200
230
  logMessage(mcpServer, "info", `Search completed: "${query}" (${searchParams}) - ${slicedResults.length} results in ${duration}ms`);
201
- return slicedResults
202
- .map((r) => `Title: ${r.title}\nDescription: ${truncateResultContent(r.content, maxResultChars)}\nURL: ${r.url}\nRelevance Score: ${r.score.toFixed(3)}`)
231
+ const formattedResults = slicedResults
232
+ .map((r) => {
233
+ const score = r.score || 0;
234
+ return `Title: ${r.title || ""}\nDescription: ${truncateResultContent(r.content || "", maxResultChars)}\nURL: ${r.url || ""}\nRelevance Score: ${score.toFixed(3)}`;
235
+ })
203
236
  .join("\n\n");
237
+ return metadata ? `${metadata}\n\n---\n\n${formattedResults}` : formattedResults;
204
238
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function performSearchSuggestions(mcpServer: McpServer, query: string, language?: string): Promise<string[]>;
@@ -0,0 +1,34 @@
1
+ import { logMessage } from "./logging.js";
2
+ import { createDefaultAgent, createProxyAgent, ProxyType } from "./proxy.js";
3
+ export async function performSearchSuggestions(mcpServer, query, language = "all") {
4
+ const base = process.env.SEARXNG_URL;
5
+ if (!base) {
6
+ return [];
7
+ }
8
+ const parsedBase = new URL(base.endsWith("/") ? base : `${base}/`);
9
+ const url = new URL("autocompleter", parsedBase);
10
+ url.searchParams.set("q", query);
11
+ if (language !== "all") {
12
+ url.searchParams.set("lang", language);
13
+ }
14
+ try {
15
+ const requestOptions = {
16
+ signal: AbortSignal.timeout(5000),
17
+ };
18
+ const proxyAgent = createProxyAgent(url.toString(), ProxyType.SEARCH);
19
+ const dispatcher = proxyAgent ?? createDefaultAgent();
20
+ if (dispatcher) {
21
+ requestOptions.dispatcher = dispatcher;
22
+ }
23
+ const response = await fetch(url.toString(), requestOptions);
24
+ if (!response.ok) {
25
+ return [];
26
+ }
27
+ const data = await response.json();
28
+ return Array.isArray(data[1]) ? data[1] : [];
29
+ }
30
+ catch {
31
+ logMessage(mcpServer, "debug", "Autocomplete request failed; returning empty suggestions");
32
+ return [];
33
+ }
34
+ }
package/dist/types.d.ts CHANGED
@@ -38,8 +38,23 @@ export declare function isSearXNGWebSearchArgs(args: unknown): args is {
38
38
  min_score?: number;
39
39
  num_results?: number;
40
40
  categories?: string;
41
+ response_format?: "text" | "json";
42
+ };
43
+ export declare function isSearXNGSearchSuggestionsArgs(args: unknown): args is {
44
+ query: string;
45
+ language?: string;
46
+ };
47
+ export declare function isSearXNGInstanceInfoArgs(args: unknown): args is {
48
+ includeEngines?: boolean;
49
+ includeDisabled?: boolean;
50
+ category?: string;
51
+ refresh?: boolean;
41
52
  };
42
53
  export declare const WEB_SEARCH_TOOL: Tool;
54
+ export declare const SUGGESTIONS_TOOL: Tool;
55
+ export declare const INSTANCE_INFO_TOOL: Tool;
43
56
  export declare const LITE_WEB_SEARCH_TOOL: Tool;
57
+ export declare const LITE_SUGGESTIONS_TOOL: Tool;
58
+ export declare const LITE_INSTANCE_INFO_TOOL: Tool;
44
59
  export declare const LITE_READ_URL_TOOL: Tool;
45
60
  export declare const READ_URL_TOOL: Tool;
package/dist/types.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const VALID_TIME_RANGES = ["day", "week", "month", "year"];
2
2
  const VALID_SAFESEARCH_VALUES = [0, 1, 2];
3
+ const VALID_RESPONSE_FORMATS = ["text", "json"];
3
4
  export function isSearXNGWebSearchArgs(args) {
4
5
  if (typeof args !== "object" ||
5
6
  args === null ||
@@ -40,6 +41,42 @@ export function isSearXNGWebSearchArgs(args) {
40
41
  if (searchArgs.categories !== undefined && typeof searchArgs.categories !== "string") {
41
42
  return false;
42
43
  }
44
+ if (searchArgs.response_format !== undefined &&
45
+ (typeof searchArgs.response_format !== "string" || !VALID_RESPONSE_FORMATS.includes(searchArgs.response_format))) {
46
+ return false;
47
+ }
48
+ return true;
49
+ }
50
+ export function isSearXNGSearchSuggestionsArgs(args) {
51
+ if (typeof args !== "object" ||
52
+ args === null ||
53
+ !("query" in args) ||
54
+ typeof args.query !== "string") {
55
+ return false;
56
+ }
57
+ const suggestionArgs = args;
58
+ if (suggestionArgs.language !== undefined && typeof suggestionArgs.language !== "string") {
59
+ return false;
60
+ }
61
+ return true;
62
+ }
63
+ export function isSearXNGInstanceInfoArgs(args) {
64
+ if (typeof args !== "object" || args === null) {
65
+ return false;
66
+ }
67
+ const infoArgs = args;
68
+ if (infoArgs.includeEngines !== undefined && typeof infoArgs.includeEngines !== "boolean") {
69
+ return false;
70
+ }
71
+ if (infoArgs.includeDisabled !== undefined && typeof infoArgs.includeDisabled !== "boolean") {
72
+ return false;
73
+ }
74
+ if (infoArgs.category !== undefined && typeof infoArgs.category !== "string") {
75
+ return false;
76
+ }
77
+ if (infoArgs.refresh !== undefined && typeof infoArgs.refresh !== "boolean") {
78
+ return false;
79
+ }
43
80
  return true;
44
81
  }
45
82
  export const WEB_SEARCH_TOOL = {
@@ -97,10 +134,73 @@ export const WEB_SEARCH_TOOL = {
97
134
  type: "string",
98
135
  description: "Comma-separated SearXNG categories. Supported: general, news, images, videos, it, science, files, social media. Default: general (SearXNG instance default).",
99
136
  },
137
+ response_format: {
138
+ type: "string",
139
+ description: "Response format: formatted text for agents or raw JSON for programmatic clients. Default: text.",
140
+ enum: ["text", "json"],
141
+ default: "text",
142
+ },
100
143
  },
101
144
  required: ["query"],
102
145
  },
103
146
  };
147
+ export const SUGGESTIONS_TOOL = {
148
+ name: "searxng_search_suggestions",
149
+ description: "Returns autocomplete suggestions from the configured SearXNG instance. " +
150
+ "Use this to refine vague or partial queries before searching.",
151
+ annotations: {
152
+ readOnlyHint: true,
153
+ openWorldHint: true,
154
+ },
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ query: {
159
+ type: "string",
160
+ description: "Partial or complete query to autocomplete.",
161
+ },
162
+ language: {
163
+ type: "string",
164
+ description: "Language code for suggestions (e.g., 'en', 'fr', 'de') or 'all'. Default: all.",
165
+ default: "all",
166
+ },
167
+ },
168
+ required: ["query"],
169
+ },
170
+ };
171
+ export const INSTANCE_INFO_TOOL = {
172
+ name: "searxng_instance_info",
173
+ description: "Discovers capabilities from the configured SearXNG instance via /config, including categories, engines, defaults, locales, and plugins.",
174
+ annotations: {
175
+ readOnlyHint: true,
176
+ openWorldHint: true,
177
+ },
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ includeEngines: {
182
+ type: "boolean",
183
+ description: "Include enabled engine names in the response.",
184
+ default: false,
185
+ },
186
+ includeDisabled: {
187
+ type: "boolean",
188
+ description: "Include disabled engine names when includeEngines is true.",
189
+ default: false,
190
+ },
191
+ category: {
192
+ type: "string",
193
+ description: "Filter categories and engines to a single category name.",
194
+ },
195
+ refresh: {
196
+ type: "boolean",
197
+ description: "Bypass the process cache and fetch fresh /config data.",
198
+ default: false,
199
+ },
200
+ },
201
+ required: [],
202
+ },
203
+ };
104
204
  export const LITE_WEB_SEARCH_TOOL = {
105
205
  name: "searxng_web_search",
106
206
  description: "Web search. Returns titles, URLs, snippets.",
@@ -110,6 +210,24 @@ export const LITE_WEB_SEARCH_TOOL = {
110
210
  required: ["query"],
111
211
  },
112
212
  };
213
+ export const LITE_SUGGESTIONS_TOOL = {
214
+ name: "searxng_search_suggestions",
215
+ description: "Autocomplete search query suggestions.",
216
+ inputSchema: {
217
+ type: "object",
218
+ properties: { query: { type: "string", description: "Query prefix." } },
219
+ required: ["query"],
220
+ },
221
+ };
222
+ export const LITE_INSTANCE_INFO_TOOL = {
223
+ name: "searxng_instance_info",
224
+ description: "Discover SearXNG instance capabilities.",
225
+ inputSchema: {
226
+ type: "object",
227
+ properties: {},
228
+ required: [],
229
+ },
230
+ };
113
231
  export const LITE_READ_URL_TOOL = {
114
232
  name: "web_url_read",
115
233
  description: "Fetch URL. Returns page text as markdown.",
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const packageVersion = "1.4.0";
1
+ export declare const packageVersion = "1.5.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const packageVersion = "1.4.0";
1
+ export const packageVersion = "1.5.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-searxng",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "mcpName": "io.github.ihor-sokoliuk/mcp-searxng",
5
5
  "description": "MCP server for SearXNG integration",
6
6
  "license": "MIT",