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.
Files changed (60) hide show
  1. __init__.py +0 -0
  2. arxiv.py +67 -0
  3. bench_prof.py +77 -0
  4. bot_detection.py +584 -0
  5. cache.py +178 -0
  6. cdp_actions.py +438 -0
  7. cdp_client.py +847 -0
  8. code_vertical.py +403 -0
  9. config.py +212 -0
  10. cookies.py +246 -0
  11. embed.py +121 -0
  12. errors.py +142 -0
  13. extract.py +168 -0
  14. fast_paths.py +1141 -0
  15. feed_extract.py +235 -0
  16. fetch.py +1326 -0
  17. fingerprint.py +68 -0
  18. focus.py +178 -0
  19. gdelt_vertical.py +116 -0
  20. ghost_state.py +362 -0
  21. grep_mcp.py +125 -0
  22. hn_extract.py +268 -0
  23. jsdata.py +542 -0
  24. mathml.py +213 -0
  25. md_convert.py +149 -0
  26. merge.py +981 -0
  27. mineru_extract.py +33 -0
  28. osv_vertical.py +174 -0
  29. pdf_extract.py +302 -0
  30. query_expand.py +90 -0
  31. recovery_tiers.py +332 -0
  32. reddit.py +465 -0
  33. reranker.py +206 -0
  34. research.py +785 -0
  35. revalidate.py +160 -0
  36. robots.py +74 -0
  37. scrapling_shim.py +48 -0
  38. screenshot.py +134 -0
  39. search_academic.py +174 -0
  40. search_anysearch.py +91 -0
  41. search_bing_rss.py +163 -0
  42. search_brave.py +89 -0
  43. search_ddg.py +149 -0
  44. search_gai.py +764 -0
  45. search_tavily.py +77 -0
  46. search_tinyfish.py +85 -0
  47. security.py +386 -0
  48. server.py +762 -0
  49. site_mapper.py +511 -0
  50. skdev_websearch_mcp-1.0.0.dist-info/METADATA +104 -0
  51. skdev_websearch_mcp-1.0.0.dist-info/RECORD +60 -0
  52. skdev_websearch_mcp-1.0.0.dist-info/WHEEL +5 -0
  53. skdev_websearch_mcp-1.0.0.dist-info/entry_points.txt +2 -0
  54. skdev_websearch_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
  55. skdev_websearch_mcp-1.0.0.dist-info/top_level.txt +54 -0
  56. stealth_cdp.py +46 -0
  57. tier_stats.py +66 -0
  58. turnstile_solve.py +212 -0
  59. verticals.py +308 -0
  60. 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())