feat: searxng MCP server for self-hosted web search
This commit is contained in:
1
.env.example
Normal file
1
.env.example
Normal file
@@ -0,0 +1 @@
|
||||
SEARXNG_URL=https://searxng.cloud-handson.com
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
.env
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.venv/
|
||||
.DS_Store
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
mcp[cli]>=1.0.0
|
||||
httpx>=0.27.0
|
||||
python-dotenv>=1.0.0
|
||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
59
src/server.py
Normal file
59
src/server.py
Normal 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()
|
||||
Reference in New Issue
Block a user