skdev-websearch-mcp 1.0.0__py3-none-any.whl
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.
- __init__.py +0 -0
- arxiv.py +67 -0
- bench_prof.py +77 -0
- bot_detection.py +584 -0
- cache.py +178 -0
- cdp_actions.py +438 -0
- cdp_client.py +847 -0
- code_vertical.py +403 -0
- config.py +212 -0
- cookies.py +246 -0
- embed.py +121 -0
- errors.py +142 -0
- extract.py +168 -0
- fast_paths.py +1141 -0
- feed_extract.py +235 -0
- fetch.py +1326 -0
- fingerprint.py +68 -0
- focus.py +178 -0
- gdelt_vertical.py +116 -0
- ghost_state.py +362 -0
- grep_mcp.py +125 -0
- hn_extract.py +268 -0
- jsdata.py +542 -0
- mathml.py +213 -0
- md_convert.py +149 -0
- merge.py +981 -0
- mineru_extract.py +33 -0
- osv_vertical.py +174 -0
- pdf_extract.py +302 -0
- query_expand.py +90 -0
- recovery_tiers.py +332 -0
- reddit.py +465 -0
- reranker.py +206 -0
- research.py +785 -0
- revalidate.py +160 -0
- robots.py +74 -0
- scrapling_shim.py +48 -0
- screenshot.py +134 -0
- search_academic.py +174 -0
- search_anysearch.py +91 -0
- search_bing_rss.py +163 -0
- search_brave.py +89 -0
- search_ddg.py +149 -0
- search_gai.py +764 -0
- search_tavily.py +77 -0
- search_tinyfish.py +85 -0
- security.py +386 -0
- server.py +762 -0
- site_mapper.py +511 -0
- skdev_websearch_mcp-1.0.0.dist-info/METADATA +104 -0
- skdev_websearch_mcp-1.0.0.dist-info/RECORD +60 -0
- skdev_websearch_mcp-1.0.0.dist-info/WHEEL +5 -0
- skdev_websearch_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- skdev_websearch_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
- skdev_websearch_mcp-1.0.0.dist-info/top_level.txt +54 -0
- stealth_cdp.py +46 -0
- tier_stats.py +66 -0
- turnstile_solve.py +212 -0
- verticals.py +308 -0
- wikipedia.py +334 -0
__init__.py
ADDED
|
File without changes
|
arxiv.py
ADDED
|
@@ -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}
|
bench_prof.py
ADDED
|
@@ -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())
|