feat: searxng MCP server for self-hosted web search

This commit is contained in:
joungmin
2026-02-28 06:38:41 +09:00
commit 6e4125c707
5 changed files with 68 additions and 0 deletions

0
src/__init__.py Normal file
View File

59
src/server.py Normal file
View File

@@ -0,0 +1,59 @@
"""MCP server wrapping the self-hosted SearXNG instance for free web search."""
import os
import sys
_project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _project_root not in sys.path:
sys.path.insert(0, _project_root)
from dotenv import load_dotenv
load_dotenv(os.path.join(_project_root, ".env"))
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(
name="searxng",
instructions="Web search via self-hosted SearXNG. Use this instead of built-in WebSearch.",
)
SEARXNG_URL = os.environ.get("SEARXNG_URL", "https://searxng.cloud-handson.com")
@mcp.tool()
def web_search(query: str, max_results: int = 10) -> list[dict]:
"""Search the web using the self-hosted SearXNG instance.
Args:
query: The search query string.
max_results: Maximum number of results to return (default 10).
Returns:
List of result dicts with keys: ``title``, ``url``, ``content`` (snippet).
"""
response = httpx.get(
f"{SEARXNG_URL}/search",
params={"q": query, "format": "json"},
timeout=15,
)
response.raise_for_status()
data = response.json()
return [
{
"title": r.get("title", ""),
"url": r.get("url", ""),
"content": r.get("content", ""),
}
for r in data.get("results", [])[:max_results]
]
def main() -> None:
mcp.run()
if __name__ == "__main__":
main()