Skip to main content
Glama
openags

Paper Search MCP

by openags

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

TableJSON Schema
NameRequiredDescriptionDefault
sourceYes
paper_idYes
doiNo
titleNo
save_pathNo./downloads
use_scihubNo
scihub_base_urlNohttps://sci-hub.se

Implementation Reference

  • 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)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/openags/paper-search-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server