skdev-websearch-mcp 1.0.0__tar.gz
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.
- skdev_websearch_mcp-1.0.0/LICENSE +21 -0
- skdev_websearch_mcp-1.0.0/PKG-INFO +104 -0
- skdev_websearch_mcp-1.0.0/README.md +58 -0
- skdev_websearch_mcp-1.0.0/__init__.py +0 -0
- skdev_websearch_mcp-1.0.0/arxiv.py +67 -0
- skdev_websearch_mcp-1.0.0/bench_prof.py +77 -0
- skdev_websearch_mcp-1.0.0/bot_detection.py +584 -0
- skdev_websearch_mcp-1.0.0/cache.py +178 -0
- skdev_websearch_mcp-1.0.0/cdp_actions.py +438 -0
- skdev_websearch_mcp-1.0.0/cdp_client.py +847 -0
- skdev_websearch_mcp-1.0.0/code_vertical.py +403 -0
- skdev_websearch_mcp-1.0.0/config.py +212 -0
- skdev_websearch_mcp-1.0.0/cookies.py +246 -0
- skdev_websearch_mcp-1.0.0/embed.py +121 -0
- skdev_websearch_mcp-1.0.0/errors.py +142 -0
- skdev_websearch_mcp-1.0.0/extract.py +168 -0
- skdev_websearch_mcp-1.0.0/fast_paths.py +1141 -0
- skdev_websearch_mcp-1.0.0/feed_extract.py +235 -0
- skdev_websearch_mcp-1.0.0/fetch.py +1326 -0
- skdev_websearch_mcp-1.0.0/fingerprint.py +68 -0
- skdev_websearch_mcp-1.0.0/focus.py +178 -0
- skdev_websearch_mcp-1.0.0/gdelt_vertical.py +116 -0
- skdev_websearch_mcp-1.0.0/ghost_state.py +362 -0
- skdev_websearch_mcp-1.0.0/grep_mcp.py +125 -0
- skdev_websearch_mcp-1.0.0/hn_extract.py +268 -0
- skdev_websearch_mcp-1.0.0/jsdata.py +542 -0
- skdev_websearch_mcp-1.0.0/mathml.py +213 -0
- skdev_websearch_mcp-1.0.0/md_convert.py +149 -0
- skdev_websearch_mcp-1.0.0/merge.py +981 -0
- skdev_websearch_mcp-1.0.0/mineru_extract.py +33 -0
- skdev_websearch_mcp-1.0.0/osv_vertical.py +174 -0
- skdev_websearch_mcp-1.0.0/pdf_extract.py +302 -0
- skdev_websearch_mcp-1.0.0/pyproject.toml +68 -0
- skdev_websearch_mcp-1.0.0/query_expand.py +90 -0
- skdev_websearch_mcp-1.0.0/recovery_tiers.py +332 -0
- skdev_websearch_mcp-1.0.0/reddit.py +465 -0
- skdev_websearch_mcp-1.0.0/reranker.py +206 -0
- skdev_websearch_mcp-1.0.0/research.py +785 -0
- skdev_websearch_mcp-1.0.0/revalidate.py +160 -0
- skdev_websearch_mcp-1.0.0/robots.py +74 -0
- skdev_websearch_mcp-1.0.0/scrapling_shim.py +48 -0
- skdev_websearch_mcp-1.0.0/screenshot.py +134 -0
- skdev_websearch_mcp-1.0.0/search_academic.py +174 -0
- skdev_websearch_mcp-1.0.0/search_anysearch.py +91 -0
- skdev_websearch_mcp-1.0.0/search_bing_rss.py +163 -0
- skdev_websearch_mcp-1.0.0/search_brave.py +89 -0
- skdev_websearch_mcp-1.0.0/search_ddg.py +149 -0
- skdev_websearch_mcp-1.0.0/search_gai.py +764 -0
- skdev_websearch_mcp-1.0.0/search_tavily.py +77 -0
- skdev_websearch_mcp-1.0.0/search_tinyfish.py +85 -0
- skdev_websearch_mcp-1.0.0/security.py +386 -0
- skdev_websearch_mcp-1.0.0/server.py +762 -0
- skdev_websearch_mcp-1.0.0/setup.cfg +4 -0
- skdev_websearch_mcp-1.0.0/site_mapper.py +511 -0
- skdev_websearch_mcp-1.0.0/skdev_websearch_mcp.egg-info/PKG-INFO +104 -0
- skdev_websearch_mcp-1.0.0/skdev_websearch_mcp.egg-info/SOURCES.txt +63 -0
- skdev_websearch_mcp-1.0.0/skdev_websearch_mcp.egg-info/dependency_links.txt +1 -0
- skdev_websearch_mcp-1.0.0/skdev_websearch_mcp.egg-info/entry_points.txt +2 -0
- skdev_websearch_mcp-1.0.0/skdev_websearch_mcp.egg-info/requires.txt +28 -0
- skdev_websearch_mcp-1.0.0/skdev_websearch_mcp.egg-info/top_level.txt +54 -0
- skdev_websearch_mcp-1.0.0/stealth_cdp.py +46 -0
- skdev_websearch_mcp-1.0.0/tier_stats.py +66 -0
- skdev_websearch_mcp-1.0.0/turnstile_solve.py +212 -0
- skdev_websearch_mcp-1.0.0/verticals.py +308 -0
- skdev_websearch_mcp-1.0.0/wikipedia.py +334 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SK-DEV-AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skdev-websearch-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Multi-engine web search + fetch MCP server for AI agents (10 tools)
|
|
5
|
+
Author: SK-DEV-AI
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SK-DEV-AI/websearch
|
|
8
|
+
Project-URL: Repository, https://github.com/SK-DEV-AI/websearch
|
|
9
|
+
Project-URL: Issues, https://github.com/SK-DEV-AI/websearch/issues
|
|
10
|
+
Keywords: mcp,mcp-server,web-search,search-engine,scraping,reranker,llm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: httpx==0.28.1
|
|
22
|
+
Requires-Dist: mcp>=2.0.0
|
|
23
|
+
Requires-Dist: trafilatura>=2.3.1
|
|
24
|
+
Requires-Dist: markdownify>=1.0.0
|
|
25
|
+
Requires-Dist: protego>=0.7.0
|
|
26
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
27
|
+
Requires-Dist: pymupdf>=1.24.0
|
|
28
|
+
Requires-Dist: ebooklib>=0.18
|
|
29
|
+
Requires-Dist: python-docx>=1.0.0
|
|
30
|
+
Requires-Dist: lxml>=6.0.0
|
|
31
|
+
Requires-Dist: extruct>=0.18.0
|
|
32
|
+
Requires-Dist: html-to-markdown>=3.0.0
|
|
33
|
+
Requires-Dist: ddgs>=9.0.0
|
|
34
|
+
Requires-Dist: websockets>=14.0
|
|
35
|
+
Provides-Extra: scraping
|
|
36
|
+
Requires-Dist: scrapling>=0.4.15; extra == "scraping"
|
|
37
|
+
Provides-Extra: extract
|
|
38
|
+
Requires-Dist: crawl4ai>=0.9.4; extra == "extract"
|
|
39
|
+
Provides-Extra: ocr
|
|
40
|
+
Requires-Dist: docling==2.118.1; extra == "ocr"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: scrapling>=0.4.15; extra == "all"
|
|
43
|
+
Requires-Dist: crawl4ai>=0.9.4; extra == "all"
|
|
44
|
+
Requires-Dist: docling==2.118.1; extra == "all"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# websearch MCP Server
|
|
48
|
+
|
|
49
|
+
[](https://skills.sh/SK-DEV-AI/skills) · `npx skills add SK-DEV-AI/skills`
|
|
50
|
+
|
|
51
|
+
Multi-engine web search + fetch MCP server. Parallel fan-out across DuckDuckGo, Bing Web/News RSS, Tavily, Wikipedia, arXiv, Google News RSS, Brave (last resort — rate-limited from some networks), Google AI Mode, and intent-gated vertical lanes (MDN, Stack Exchange, GitHub, HN, Semantic Scholar, OSV vuln, grep.app/Sourcegraph code, GDELT news) — with consensus-merge dedup, a local cross-encoder reranker, and Groq synthesis with grounding checks.
|
|
52
|
+
|
|
53
|
+
## Tools (10)
|
|
54
|
+
|
|
55
|
+
- **search** — Open research questions. depth=1 snippets, depth≥2 full-page fetch + rerank. `synthesize` returns a Groq answer with [N] citations.
|
|
56
|
+
- **fetch** — URL to markdown/text. Direct fetch with auto-fallback to the Helium CDP browser for blocked/JS pages. PDF/EPUB/DOCX supported. Paginated (`offset`). For structured JSON use `extract`.
|
|
57
|
+
- **screenshot** — ARIA accessibility snapshot (LLM-readable page text and interactive structure).
|
|
58
|
+
- **browser_action** — Drive a real browser page (navigate, click, type, press a key, evaluate JS, read HTML/text, ARIA tree, screenshot). Use when a page needs interaction; for plain reading use `fetch`.
|
|
59
|
+
- **wikipedia** — Full Wikipedia API client: articles, summaries, categories, links, pageviews, revisions, backlinks, geosearch, recent changes, random pages.
|
|
60
|
+
- **arxiv** — arXiv paper search with boolean/phrase/wildcard query syntax (single-source; multi-source paper search lives in codesearch `papers`).
|
|
61
|
+
- **extract** — Structured JSON from a page via CSS (fast, free), LLM, or regex strategies. Repeat calls cached.
|
|
62
|
+
- **pdf_extract** — PDF to markdown/json/html. PyMuPDF fast path for text-layer PDFs, Docling+OCR fallback for scanned PDFs.
|
|
63
|
+
- **map_site** — Survey a site's pages via sitemap XML (primary) + HTML link fallback, before fetching individually.
|
|
64
|
+
- **ping** — Connectivity check for the internet and key search endpoints. Use before expensive calls when uncertain.
|
|
65
|
+
|
|
66
|
+
## Setup
|
|
67
|
+
|
|
68
|
+
Requires Python 3.14+.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
72
|
+
pip install -r requirements.txt
|
|
73
|
+
cp run.example run # fill in your API keys (run is gitignored — never commit it)
|
|
74
|
+
chmod +x run
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Point your MCP client at `run` (stdio):
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{ "websearch": { "command": "/path/to/websearch/run" } }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### API keys (`run`)
|
|
84
|
+
|
|
85
|
+
| Key | Used for | Required? |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `GROQ_API_KEYS` | query rewrite, synthesis, grounding checks | Recommended (search works without, no synthesis) |
|
|
88
|
+
| `TAVILY_KEYS` | Tavily engine lane | Optional |
|
|
89
|
+
| `NV_KEY` | NVIDIA embedding endpoint | Optional |
|
|
90
|
+
|
|
91
|
+
Everything else (DDG, Bing RSS, Wikipedia, arXiv, RSS) is keyless. Brave's HTML scrape is currently rate-limited from some networks, so Bing RSS is the primary general-web door and Brave is a last resort.
|
|
92
|
+
|
|
93
|
+
## Notes
|
|
94
|
+
|
|
95
|
+
- **Reranker (optional, local):** ranking uses a local `gte-reranker-modernbert-base` cross-encoder over a Unix socket (`RERANKER_MODEL` overrides the model). Without it, results return unranked with `relevance_score 0.0` — everything still works.
|
|
96
|
+
- **Helium CDP (optional):** bot-walled pages escalate to a Chrome DevTools browser at `127.0.0.1:9222` when available; without it those pages return a terminal verdict instead of hanging.
|
|
97
|
+
- **Caching:** fetch results cache for 1h (`cache_ttl=0` forces fresh). No database, no state beyond the cache dir.
|
|
98
|
+
- **Safety:** all user-URL fetches are SSRF-validated (private-IP blocking + redirect-hop revalidation + robots.txt gate).
|
|
99
|
+
- **Groq model split:** synthesis + grounding + extract-llm use `openai/gpt-oss-120b`; rewrite/classify/expand/decompose use `openai/gpt-oss-20b` (the previous `llama-3.1-8b-instant` was retired by Groq and now 404s). Constants `GROQ_SMART_MODEL`/`GROQ_CHEAP_MODEL` in config.py.
|
|
100
|
+
- **Keyless lanes (all advisory-only):** OSV.dev vuln vertical + NVD CVE enrichment; grep.app MCP code search (Sourcegraph backup); Bing Web RSS + Bing News RSS (Brave's scrape is rate-limited, GDELT 429s from some networks); Wikipedia vertical; Jina Reader pre-CDP tier; Wayback CDX-history recovery in enrich(); Common Crawl domain-survey assist in map_site.
|
|
101
|
+
- **Local reranker:** a `gte-reranker-modernbert-base` cross-encoder runs as one shared worker over a Unix socket; either server spawns it on demand and it unloads when idle. Optional — without it results are engine-ranked only.
|
|
102
|
+
- **Optional keys (graceful keyless fallback):** `SE_KEY`/`STACKEXCHANGE_KEY` (10k/day SE), `S2_KEY`/`S2_API_KEY` (dedicated S2 1 RPS).
|
|
103
|
+
- **Marginalia:** best-effort lane only (shared public key pool, 503s under load); independent-index recall, no dedicated key wired (kontakt@marginalia.nu for one).
|
|
104
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# websearch MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://skills.sh/SK-DEV-AI/skills) · `npx skills add SK-DEV-AI/skills`
|
|
4
|
+
|
|
5
|
+
Multi-engine web search + fetch MCP server. Parallel fan-out across DuckDuckGo, Bing Web/News RSS, Tavily, Wikipedia, arXiv, Google News RSS, Brave (last resort — rate-limited from some networks), Google AI Mode, and intent-gated vertical lanes (MDN, Stack Exchange, GitHub, HN, Semantic Scholar, OSV vuln, grep.app/Sourcegraph code, GDELT news) — with consensus-merge dedup, a local cross-encoder reranker, and Groq synthesis with grounding checks.
|
|
6
|
+
|
|
7
|
+
## Tools (10)
|
|
8
|
+
|
|
9
|
+
- **search** — Open research questions. depth=1 snippets, depth≥2 full-page fetch + rerank. `synthesize` returns a Groq answer with [N] citations.
|
|
10
|
+
- **fetch** — URL to markdown/text. Direct fetch with auto-fallback to the Helium CDP browser for blocked/JS pages. PDF/EPUB/DOCX supported. Paginated (`offset`). For structured JSON use `extract`.
|
|
11
|
+
- **screenshot** — ARIA accessibility snapshot (LLM-readable page text and interactive structure).
|
|
12
|
+
- **browser_action** — Drive a real browser page (navigate, click, type, press a key, evaluate JS, read HTML/text, ARIA tree, screenshot). Use when a page needs interaction; for plain reading use `fetch`.
|
|
13
|
+
- **wikipedia** — Full Wikipedia API client: articles, summaries, categories, links, pageviews, revisions, backlinks, geosearch, recent changes, random pages.
|
|
14
|
+
- **arxiv** — arXiv paper search with boolean/phrase/wildcard query syntax (single-source; multi-source paper search lives in codesearch `papers`).
|
|
15
|
+
- **extract** — Structured JSON from a page via CSS (fast, free), LLM, or regex strategies. Repeat calls cached.
|
|
16
|
+
- **pdf_extract** — PDF to markdown/json/html. PyMuPDF fast path for text-layer PDFs, Docling+OCR fallback for scanned PDFs.
|
|
17
|
+
- **map_site** — Survey a site's pages via sitemap XML (primary) + HTML link fallback, before fetching individually.
|
|
18
|
+
- **ping** — Connectivity check for the internet and key search endpoints. Use before expensive calls when uncertain.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
Requires Python 3.14+.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
26
|
+
pip install -r requirements.txt
|
|
27
|
+
cp run.example run # fill in your API keys (run is gitignored — never commit it)
|
|
28
|
+
chmod +x run
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Point your MCP client at `run` (stdio):
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{ "websearch": { "command": "/path/to/websearch/run" } }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### API keys (`run`)
|
|
38
|
+
|
|
39
|
+
| Key | Used for | Required? |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `GROQ_API_KEYS` | query rewrite, synthesis, grounding checks | Recommended (search works without, no synthesis) |
|
|
42
|
+
| `TAVILY_KEYS` | Tavily engine lane | Optional |
|
|
43
|
+
| `NV_KEY` | NVIDIA embedding endpoint | Optional |
|
|
44
|
+
|
|
45
|
+
Everything else (DDG, Bing RSS, Wikipedia, arXiv, RSS) is keyless. Brave's HTML scrape is currently rate-limited from some networks, so Bing RSS is the primary general-web door and Brave is a last resort.
|
|
46
|
+
|
|
47
|
+
## Notes
|
|
48
|
+
|
|
49
|
+
- **Reranker (optional, local):** ranking uses a local `gte-reranker-modernbert-base` cross-encoder over a Unix socket (`RERANKER_MODEL` overrides the model). Without it, results return unranked with `relevance_score 0.0` — everything still works.
|
|
50
|
+
- **Helium CDP (optional):** bot-walled pages escalate to a Chrome DevTools browser at `127.0.0.1:9222` when available; without it those pages return a terminal verdict instead of hanging.
|
|
51
|
+
- **Caching:** fetch results cache for 1h (`cache_ttl=0` forces fresh). No database, no state beyond the cache dir.
|
|
52
|
+
- **Safety:** all user-URL fetches are SSRF-validated (private-IP blocking + redirect-hop revalidation + robots.txt gate).
|
|
53
|
+
- **Groq model split:** synthesis + grounding + extract-llm use `openai/gpt-oss-120b`; rewrite/classify/expand/decompose use `openai/gpt-oss-20b` (the previous `llama-3.1-8b-instant` was retired by Groq and now 404s). Constants `GROQ_SMART_MODEL`/`GROQ_CHEAP_MODEL` in config.py.
|
|
54
|
+
- **Keyless lanes (all advisory-only):** OSV.dev vuln vertical + NVD CVE enrichment; grep.app MCP code search (Sourcegraph backup); Bing Web RSS + Bing News RSS (Brave's scrape is rate-limited, GDELT 429s from some networks); Wikipedia vertical; Jina Reader pre-CDP tier; Wayback CDX-history recovery in enrich(); Common Crawl domain-survey assist in map_site.
|
|
55
|
+
- **Local reranker:** a `gte-reranker-modernbert-base` cross-encoder runs as one shared worker over a Unix socket; either server spawns it on demand and it unloads when idle. Optional — without it results are engine-ranked only.
|
|
56
|
+
- **Optional keys (graceful keyless fallback):** `SE_KEY`/`STACKEXCHANGE_KEY` (10k/day SE), `S2_KEY`/`S2_API_KEY` (dedicated S2 1 RPS).
|
|
57
|
+
- **Marginalia:** best-effort lane only (shared public key pool, 503s under load); independent-index recall, no dedicated key wired (kontakt@marginalia.nu for one).
|
|
58
|
+
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from config import get_http_client
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def search_arxiv(query: str, count: int = 3, search_field: str = "all",
|
|
10
|
+
sort_by: str = "relevance", sort_order: str = "descending",
|
|
11
|
+
start: int = 0, id_list: str = "",
|
|
12
|
+
category: str = "", raw_query: str = "") -> dict:
|
|
13
|
+
try:
|
|
14
|
+
if raw_query:
|
|
15
|
+
params = {"search_query": raw_query, "start": start,
|
|
16
|
+
"max_results": min(count, 50), "sortBy": sort_by, "sortOrder": sort_order}
|
|
17
|
+
elif id_list:
|
|
18
|
+
params = {"id_list": id_list, "start": start,
|
|
19
|
+
"max_results": min(count, 50), "sortBy": sort_by, "sortOrder": sort_order}
|
|
20
|
+
else:
|
|
21
|
+
search_q = f"{search_field}:{query}" if search_field != "all" else f"all:{query}"
|
|
22
|
+
if category:
|
|
23
|
+
search_q += f" AND cat:{category}"
|
|
24
|
+
params = {"search_query": search_q, "start": start,
|
|
25
|
+
"max_results": min(count, 50), "sortBy": sort_by, "sortOrder": sort_order}
|
|
26
|
+
c = get_http_client()
|
|
27
|
+
r = await c.get("https://export.arxiv.org/api/query", params=params,
|
|
28
|
+
headers={"User-Agent": "mcp-codesearch/1.0"}, timeout=15)
|
|
29
|
+
if r.status_code != 200:
|
|
30
|
+
return {"results": [], "total_available": 0}
|
|
31
|
+
ns = {"atom": "http://www.w3.org/2005/Atom",
|
|
32
|
+
"arxiv": "http://arxiv.org/schemas/atom"}
|
|
33
|
+
root = ET.fromstring(r.text)
|
|
34
|
+
total = root.findtext("opensearch:totalResults", "0",
|
|
35
|
+
{"opensearch": "http://a9.com/-/spec/opensearch/1.1/"})
|
|
36
|
+
results = []
|
|
37
|
+
for entry in root.findall("atom:entry", ns)[:count]:
|
|
38
|
+
title = entry.findtext("atom:title", "", ns).replace("\n", " ").strip()
|
|
39
|
+
summary_text = entry.findtext("atom:summary", "", ns).replace("\n", " ").strip()[:500]
|
|
40
|
+
url = ""
|
|
41
|
+
for link in entry.findall("atom:link", ns):
|
|
42
|
+
if link.get("title") == "pdf":
|
|
43
|
+
url = link.get("href", "")
|
|
44
|
+
break
|
|
45
|
+
if link.get("rel") == "alternate":
|
|
46
|
+
url = link.get("href", "")
|
|
47
|
+
authors = [a.findtext("atom:name", "", ns) for a in entry.findall("atom:author", ns)]
|
|
48
|
+
published = entry.findtext("atom:published", "", ns)[:10]
|
|
49
|
+
updated = entry.findtext("atom:updated", "", ns)[:10]
|
|
50
|
+
categories = [c.get("term", "") for c in entry.findall("atom:category", ns)]
|
|
51
|
+
comment = entry.findtext("arxiv:comment", "", ns)
|
|
52
|
+
journal_ref = entry.findtext("arxiv:journal_ref", "", ns)
|
|
53
|
+
doi = entry.findtext("arxiv:doi", "", ns)
|
|
54
|
+
pc = entry.find("arxiv:primary_category", ns)
|
|
55
|
+
primary_cat = pc.get("term", "") if pc is not None else ""
|
|
56
|
+
id_text = entry.findtext("atom:id", "", ns)
|
|
57
|
+
arxiv_id = id_text.split("/abs/")[-1] if "/abs/" in id_text else ""
|
|
58
|
+
results.append({
|
|
59
|
+
"title": title, "url": url, "snippet": summary_text,
|
|
60
|
+
"authors": authors, "published": published, "updated": updated,
|
|
61
|
+
"source": "arxiv", "arxiv_id": arxiv_id,
|
|
62
|
+
"categories": categories, "primary_category": primary_cat,
|
|
63
|
+
"comment": comment, "journal_ref": journal_ref, "doi": doi,
|
|
64
|
+
})
|
|
65
|
+
return {"results": results, "total_available": int(total)}
|
|
66
|
+
except Exception:
|
|
67
|
+
return {"results": [], "total_available": 0}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Detailed end-to-end benchmark of websearch search_multi with all engine keys loaded.
|
|
2
|
+
|
|
3
|
+
Diagnostic only. Loads env from the server's `run` file via bash (so ${VAR:-default}
|
|
4
|
+
expands exactly as the server sees), instruments every engine + phase on the `research`
|
|
5
|
+
namespace, runs search_multi once, prints a per-phase bill.
|
|
6
|
+
"""
|
|
7
|
+
import asyncio, os, subprocess, sys, time, tempfile
|
|
8
|
+
|
|
9
|
+
WSDIR = "/home/sk/session-root/repos/websearch"
|
|
10
|
+
|
|
11
|
+
res = subprocess.run(["bash", "-c",
|
|
12
|
+
f"source <(grep -E '^export ' {WSDIR}/run); env | grep -E '^(GROQ|TAVILY|TINYFISH|ANYSEARCH|NV|RERANKER)_'"],
|
|
13
|
+
capture_output=True, text=True)
|
|
14
|
+
for line in res.stdout.splitlines():
|
|
15
|
+
if "=" in line:
|
|
16
|
+
k, _, v = line.partition("=")
|
|
17
|
+
os.environ[k] = v
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, WSDIR)
|
|
20
|
+
import research
|
|
21
|
+
|
|
22
|
+
from collections import defaultdict
|
|
23
|
+
TIMES = defaultdict(float)
|
|
24
|
+
COUNTS = defaultdict(int)
|
|
25
|
+
RESULTS = defaultdict(int)
|
|
26
|
+
_t0 = {}
|
|
27
|
+
|
|
28
|
+
def _timeit(name):
|
|
29
|
+
def deco(fn):
|
|
30
|
+
async def wrap(*a, **k):
|
|
31
|
+
key = name
|
|
32
|
+
if key not in _t0:
|
|
33
|
+
_t0[key] = time.monotonic()
|
|
34
|
+
r = await fn(*a, **k)
|
|
35
|
+
dt = time.monotonic() - _t0.pop(key, time.monotonic())
|
|
36
|
+
TIMES[key] += dt
|
|
37
|
+
COUNTS[key] += 1
|
|
38
|
+
if isinstance(r, dict):
|
|
39
|
+
RESULTS[key] += len(r.get("results", []))
|
|
40
|
+
elif isinstance(r, list):
|
|
41
|
+
RESULTS[key] += len(r)
|
|
42
|
+
return r
|
|
43
|
+
return wrap
|
|
44
|
+
return deco
|
|
45
|
+
|
|
46
|
+
_existing = {}
|
|
47
|
+
def patch(mod, name):
|
|
48
|
+
_existing[name] = getattr(mod, name)
|
|
49
|
+
setattr(mod, name, _timeit(name)(_existing[name]))
|
|
50
|
+
|
|
51
|
+
# Engine phases in research namespace
|
|
52
|
+
for fn in ["search_google_rss", "search_tavily", "search_wikipedia", "search_reddit",
|
|
53
|
+
"search_arxiv", "search_anysearch", "tinyfish_search", "search_ddg",
|
|
54
|
+
"search_brave"]:
|
|
55
|
+
if hasattr(research, fn):
|
|
56
|
+
patch(research, fn)
|
|
57
|
+
# Groq + rerank phases
|
|
58
|
+
for fn in ["rewrite_query", "classify_need", "decompose_query", "expand_query", "_rerank"]:
|
|
59
|
+
if hasattr(research, fn):
|
|
60
|
+
patch(research, fn)
|
|
61
|
+
|
|
62
|
+
query = sys.argv[1] if len(sys.argv) > 1 else "best laptops 2026 under 1000 dollars"
|
|
63
|
+
count = int(sys.argv[2]) if len(sys.argv) > 2 else 50
|
|
64
|
+
|
|
65
|
+
async def main():
|
|
66
|
+
t0 = time.monotonic()
|
|
67
|
+
from config import TAVILY_KEYS, TINYFISH_KEYS, GROQ_API_KEYS, NV_KEY
|
|
68
|
+
print(f"keys: tavily={len(TAVILY_KEYS)} tinyfish={len(TINYFISH_KEYS)} groq={len(GROQ_API_KEYS)} nv={'y' if NV_KEY else 'n'}", flush=True)
|
|
69
|
+
r = await research.search_multi(query, count=count, tavily_depth="advanced")
|
|
70
|
+
twall = time.monotonic() - t0
|
|
71
|
+
print(f"\n=== SEARCH_MULTI q={query!r} count={count} ===")
|
|
72
|
+
print(f"{'phase':<22}{'sec':>8}{'calls':>6}{'res':>6}")
|
|
73
|
+
for k in sorted(TIMES, key=lambda x: -TIMES[x]):
|
|
74
|
+
print(f"{k:<22}{TIMES[k]:>8.2f}{COUNTS[k]:>6}{RESULTS[k]:>6}")
|
|
75
|
+
print(f"\nTOTAL WALL: {twall:.1f}s results={len(r.get('results', []))}")
|
|
76
|
+
|
|
77
|
+
asyncio.run(main())
|