Skip to main content
Glama

SearXNG Server

#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, LoggingLevel, } from "@modelcontextprotocol/sdk/types.js"; // Import modularized functionality import { WEB_SEARCH_TOOL, READ_URL_TOOL, isSearXNGWebSearchArgs } from "./types.js"; import { logMessage, setLogLevel } from "./logging.js"; import { performWebSearch } from "./search.js"; import { fetchAndConvertToMarkdown } from "./url-reader.js"; import { createConfigResource, createHelpResource } from "./resources.js"; import { createHttpServer } from "./http-server.js"; import { validateEnvironment as validateEnv } from "./error-handler.js"; // Use a static version string that will be updated by the version script const packageVersion = "0.6.5"; // Export the version for use in other modules export { packageVersion }; // Global state for logging level let currentLogLevel: LoggingLevel = "info"; // Type guard for URL reading args export function isWebUrlReadArgs(args: unknown): args is { url: string } { return ( typeof args === "object" && args !== null && "url" in args && typeof (args as { url: string }).url === "string" ); } // Server implementation const server = new Server( { name: "ihor-sokoliuk/mcp-searxng", version: packageVersion, }, { capabilities: { logging: {}, resources: {}, tools: { searxng_web_search: { description: WEB_SEARCH_TOOL.description, schema: WEB_SEARCH_TOOL.inputSchema, }, web_url_read: { description: READ_URL_TOOL.description, schema: READ_URL_TOOL.inputSchema, }, }, }, } ); // List tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { logMessage(server, "debug", "Handling list_tools request"); return { tools: [WEB_SEARCH_TOOL, READ_URL_TOOL], }; }); // Call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; logMessage(server, "debug", `Handling call_tool request: ${name}`); try { if (name === "searxng_web_search") { if (!isSearXNGWebSearchArgs(args)) { throw new Error("Invalid arguments for web search"); } const result = await performWebSearch( server, args.query, args.pageno, args.time_range, args.language, args.safesearch ); return { content: [ { type: "text", text: result, }, ], }; } else if (name === "web_url_read") { if (!isWebUrlReadArgs(args)) { throw new Error("Invalid arguments for URL reading"); } const result = await fetchAndConvertToMarkdown(server, args.url); return { content: [ { type: "text", text: result, }, ], }; } else { throw new Error(`Unknown tool: ${name}`); } } catch (error) { logMessage(server, "error", `Tool execution error: ${error instanceof Error ? error.message : String(error)}`, { tool: name, args: args, error: error instanceof Error ? error.stack : String(error) }); throw error; } }); // Logging level handler server.setRequestHandler(SetLevelRequestSchema, async (request) => { const { level } = request.params; logMessage(server, "info", `Setting log level to: ${level}`); currentLogLevel = level; setLogLevel(level); return {}; }); // List resources handler server.setRequestHandler(ListResourcesRequestSchema, async () => { logMessage(server, "debug", "Handling list_resources request"); return { resources: [ { uri: "config://server-config", mimeType: "application/json", name: "Server Configuration", description: "Current server configuration and environment variables" }, { uri: "help://usage-guide", mimeType: "text/markdown", name: "Usage Guide", description: "How to use the MCP SearXNG server effectively" } ] }; }); // Read resource handler server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; logMessage(server, "debug", `Handling read_resource request for: ${uri}`); switch (uri) { case "config://server-config": return { contents: [ { uri: uri, mimeType: "application/json", text: createConfigResource() } ] }; case "help://usage-guide": return { contents: [ { uri: uri, mimeType: "text/markdown", text: createHelpResource() } ] }; default: throw new Error(`Unknown resource: ${uri}`); } }); // Main function async function main() { // Environment validation const validationError = validateEnv(); if (validationError) { console.error(`❌ ${validationError}`); process.exit(1); } // Check for HTTP transport mode const httpPort = process.env.MCP_HTTP_PORT; if (httpPort) { const port = parseInt(httpPort, 10); if (isNaN(port) || port < 1 || port > 65535) { console.error(`Invalid HTTP port: ${httpPort}. Must be between 1-65535.`); process.exit(1); } console.log(`Starting HTTP transport on port ${port}`); const app = await createHttpServer(server); const httpServer = app.listen(port, () => { console.log(`HTTP server listening on port ${port}`); console.log(`Health check: http://localhost:${port}/health`); console.log(`MCP endpoint: http://localhost:${port}/mcp`); }); // Handle graceful shutdown const shutdown = (signal: string) => { console.log(`Received ${signal}. Shutting down HTTP server...`); httpServer.close(() => { console.log("HTTP server closed"); process.exit(0); }); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); } else { // Default STDIO transport // Show helpful message when running in terminal if (process.stdin.isTTY) { console.log(`🔍 MCP SearXNG Server v${packageVersion} - Ready`); console.log("✅ Configuration valid"); console.log(`🌐 SearXNG URL: ${process.env.SEARXNG_URL}`); console.log("📡 Waiting for MCP client connection via STDIO...\n"); } const transport = new StdioServerTransport(); await server.connect(transport); // Log after connection is established logMessage(server, "info", `MCP SearXNG Server v${packageVersion} connected via STDIO`); logMessage(server, "info", `Log level: ${currentLogLevel}`); logMessage(server, "info", `Environment: ${process.env.NODE_ENV || 'development'}`); logMessage(server, "info", `SearXNG URL: ${process.env.SEARXNG_URL || 'not configured'}`); } } // Handle uncaught errors process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); process.exit(1); }); // Start the server (CLI entrypoint) main().catch((error) => { console.error("Failed to start server:", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ihor-sokoliuk/mcp-searxng'

If you have feedback or need assistance with the MCP directory API, please join our Discord server