download_with_fallback
Download academic papers by attempting source-native access first, then open-access repositories, with optional Sci-Hub fallback when other methods fail.
Instructions
Try source-native download, OA repositories, Unpaywall, then optional Sci-Hub.
Args: source: Source name (arxiv, biorxiv, medrxiv, iacr, semantic, crossref, pubmed, pmc, core, europepmc, citeseerx, doaj, base, zenodo, hal, ssrn). paper_id: Source-native paper identifier. doi: Optional DOI used for repository/unpaywall/Sci-Hub fallback. title: Optional title used for repository/Sci-Hub fallback when DOI is unavailable. save_path: Directory to save downloaded files. use_scihub: Whether to fallback to Sci-Hub after OA attempts fail. scihub_base_url: Sci-Hub mirror URL for fallback. Returns: Download path on success or explanatory error message.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| source | Yes | ||
| paper_id | Yes | ||
| doi | No | ||
| title | No | ||
| save_path | No | ./downloads | |
| use_scihub | No | ||
| scihub_base_url | No | https://sci-hub.se |
Implementation Reference
- paper_search_mcp/server.py:753-842 (handler)The implementation of the 'download_with_fallback' tool, which attempts to download a paper from a primary source, then tries repositories, Unpaywall, and optionally Sci-Hub.
async def download_with_fallback( source: str, paper_id: str, doi: str = "", title: str = "", save_path: str = "./downloads", use_scihub: bool = True, scihub_base_url: str = "https://sci-hub.se", ) -> str: """Try source-native download, OA repositories, Unpaywall, then optional Sci-Hub. Args: source: Source name (arxiv, biorxiv, medrxiv, iacr, semantic, crossref, pubmed, pmc, core, europepmc, citeseerx, doaj, base, zenodo, hal, ssrn). paper_id: Source-native paper identifier. doi: Optional DOI used for repository/unpaywall/Sci-Hub fallback. title: Optional title used for repository/Sci-Hub fallback when DOI is unavailable. save_path: Directory to save downloaded files. use_scihub: Whether to fallback to Sci-Hub after OA attempts fail. scihub_base_url: Sci-Hub mirror URL for fallback. Returns: Download path on success or explanatory error message. """ source_name = source.strip().lower() primary_downloaders = { "arxiv": arxiv_searcher.download_pdf, "biorxiv": biorxiv_searcher.download_pdf, "medrxiv": medrxiv_searcher.download_pdf, "iacr": iacr_searcher.download_pdf, "semantic": semantic_searcher.download_pdf, "pubmed": pubmed_searcher.download_pdf, "crossref": crossref_searcher.download_pdf, "pmc": pmc_searcher.download_pdf, "core": core_searcher.download_pdf, "europepmc": europepmc_searcher.download_pdf, "citeseerx": citeseerx_searcher.download_pdf, "doaj": doaj_searcher.download_pdf, "base": base_searcher.download_pdf, "zenodo": zenodo_searcher.download_pdf, "hal": hal_searcher.download_pdf, "ssrn": ssrn_searcher.download_pdf, } attempt_errors: List[str] = [] primary_error = "" if source_name in primary_downloaders: try: primary_result = await asyncio.to_thread(primary_downloaders[source_name], paper_id, save_path) if isinstance(primary_result, str) and os.path.exists(primary_result): return primary_result if isinstance(primary_result, str) and primary_result: primary_error = primary_result except Exception as exc: primary_error = str(exc) logger.warning("Primary download failed for %s/%s: %s", source_name, paper_id, exc) else: primary_error = f"Unsupported source '{source_name}' for primary download." if primary_error: attempt_errors.append(f"primary: {primary_error}") repository_result, repository_error = await _try_repository_fallback(doi, title, save_path) if repository_result: return repository_result if repository_error: attempt_errors.append(f"repositories: {repository_error}") normalized_doi = (doi or "").strip() if normalized_doi: unpaywall_url = await asyncio.to_thread(unpaywall_resolver.resolve_best_pdf_url, normalized_doi) if unpaywall_url: unpaywall_result = await _download_from_url(unpaywall_url, save_path, f"unpaywall_{normalized_doi}") if unpaywall_result: return unpaywall_result attempt_errors.append("unpaywall: resolved OA URL but download failed") else: attempt_errors.append("unpaywall: no OA URL found (or PAPER_SEARCH_MCP_UNPAYWALL_EMAIL/UNPAYWALL_EMAIL missing)") else: attempt_errors.append("unpaywall: DOI not provided") if not use_scihub: return "Download failed after OA fallback chain. Details: " + " | ".join(attempt_errors) fallback_identifier = (doi or "").strip() or (title or "").strip() or paper_id fetcher = SciHubFetcher(base_url=scihub_base_url, output_dir=save_path) fallback_result = await asyncio.to_thread(fetcher.download_pdf, fallback_identifier) if fallback_result: return fallback_result return "Download failed after OA fallback chain and Sci-Hub fallback. Details: " + " | ".join(attempt_errors)