Source code for klea_utils.mcp.tool_impls.papers.arxiv

#!/usr/bin/env python3
"""
arXiv paper search source (general preprints).

File: klea_utils/mcp/tool_impls/papers/arxiv.py

Copyright 2026 Ankur Sinha
Author: Ankur Sinha <sanjay DOT ankur AT gmail DOT com>
"""

import logging
import re
import xml.etree.ElementTree as ET

from klea_utils.mcp.tool_impls.session import SessionLike

from .errors import PaperSourceError
from .http import Throttle, get_text
from .record import PaperRecord, clean_doi

logger = logging.getLogger(__name__)

ARXIV_QUERY_URL = "https://export.arxiv.org/api/query"
_ATOM = "{http://www.w3.org/2005/Atom}"
_ARXIV = "{http://arxiv.org/schemas/atom}"

#: arXiv asks for at most one request every 3 seconds and blocks clients
#: that go faster for several minutes.
_THROTTLE = Throttle(min_interval=3.0)


def _text(element: ET.Element, tag: str) -> str | None:
    child = element.find(tag)
    if child is None or not child.text:
        return None
    return re.sub(r"\s+", " ", child.text).strip() or None


def _normalize(entry: ET.Element) -> PaperRecord:
    published = _text(entry, f"{_ATOM}published") or ""
    year = int(published[:4]) if published[:4].isdigit() else None
    authors = [
        name
        for author in entry.findall(f"{_ATOM}author")
        if (name := _text(author, f"{_ATOM}name"))
    ]
    page = next(
        (
            link.get("href")
            for link in entry.findall(f"{_ATOM}link")
            if link.get("rel") == "alternate" and link.get("href")
        ),
        None,
    )
    return PaperRecord(
        title=_text(entry, f"{_ATOM}title"),
        authors=authors,
        year=year,
        journal="arXiv",
        abstract=_text(entry, f"{_ATOM}summary"),
        doi=clean_doi(_text(entry, f"{_ARXIV}doi")),
        url=page or _text(entry, f"{_ATOM}id"),
        peer_reviewed=False,
        source="arxiv",
    )