Skip to content

MCP server

bibr mcp runs a Model Context Protocol server over stdio, so agents (Claude Code, Claude Desktop, any MCP client) can extract papers and query the results as tools — no shelling out to the CLI, no parsing whole export files into the agent's context.

One warm pipeline serves the whole session: it is created on the first extraction call and reused by later calls. Model residency follows the configured memory mode, just as it does with bibr.Chewer.

Setup

The server uses the MCP Python SDK v2 (minimum 2.2.0) and needs the mcp extra:

uv sync --extra mcp        # add MCP to a source checkout
uv add 'bibr[mcp]'         # add MCP to a project that installed bibr from PyPI

Keep the extras required by your selected OCR/runtime as well; for example, uv sync --extra all --extra vllm includes MCP, ML models, and the CUDA vLLM runtime. See Installation for platform choices. Loading and querying saved exports does not load extraction models and does not need LLM credentials: if the LLM check at startup fails (no API key, no local launcher), the server still starts, logs the problem on stderr, and chew_paper / chew_url return it as a tool error.

Register it with your client — for Claude Code, from your project directory:

claude mcp add bibr -- uv run bibr mcp

or in JSON client configs (Claude Desktop and most others):

{
  "mcpServers": {
    "bibr": {
      "command": "uv",
      "args": ["--directory", "/path/to/bibr", "run", "bibr", "mcp",
               "--allow-dir", "/path/to/papers"]
    }
  }
}

uv --directory also sets the directory the server starts in, so the second example names the papers directory with --allow-dir; see File access.

Pipeline options are fixed at server start (they configure the shared warm pipeline, exactly like bibr.Chewer): pass a subset of the bibr chew flags after bibr mcp, e.g. bibr mcp --refs ner --crossref (enrichment is off unless --crossref or CROSSREF_ENRICH=true turns it on). Everything else (provider keys, model choice) comes from your .env / settings as usual.

The HTTP endpoint keeps papers within an initialized MCP session and negotiates protocol 2025-11-25. SDK v2 clients in automatic mode negotiate this protocol; clients configured for the sessionless 2026-07-28 protocol must enable automatic or legacy negotiation. The stdio server also supports the newer protocol because its process already belongs to a single client.

Exports and query results use schema v12. get_metadata returns its paper fields under metadata; summaries expose LLM usage as totals and breakdown within the existing llm_usage summary field.

The tool surface

A full export is far too large for a single tool result (sentence-level spans, table HTML, optionally base64 figures), so the tools follow a chew once, query granularly contract: extraction returns only a compact summary plus a paper_id, and the query tools read slices on demand.

Tool What it returns
chew_paper(path, paper_id?) Runs the pipeline on one file in an allowed directory; compact summary (title, DOI, counts, validation, LLM token usage) + paper_id
chew_url(url, paper_id?) Downloads a public https:// URL (SSRF-guarded — see below) and runs the same extraction
load_paper(path) Registers an existing bibr export JSON (a regular file of at most 256 MB in an allowed directory) without re-processing; same summary
list_papers() Papers loaded this session (id, title, DOI, source)
get_paper_summary(paper_id) The compact summary again
get_metadata(paper_id) Full metadata block (title, abstract, journal, paper type, integrity statements, …), authors, affiliations, funding
get_sections(paper_id) Section hierarchy with IMRaD-style types and per-section sentence counts
get_text(paper_id, section_id?, page?, offset?, limit?) Sentence spans with text_id/section_id/page_number, filtered and paginated
search_text(paper_id, query, limit?) Case-insensitive substring search over sentences
get_references(paper_id, offset?, limit?) Parsed bibliography entries, paginated, empty fields omitted
get_reference_citations(paper_id, bib_id) Every in-text citation of one reference, with the full source sentence and page
export_references(paper_id, format?, prefer?) The reference list as the text of one CSL-JSON (default), BibTeX or RIS file, with its content type and reference count (see Reference-manager files)
get_tables(paper_id, table_id?) Printed label and caption per table; full HTML + cells for one table_id
get_figures(paper_id, figure_id?) Captions and pages; the image is replaced by has_image
save_paper(paper_id, path, compact?, overwrite?) Writes the complete export JSON to a .json path in an allowed directory; refuses to overwrite an existing file unless overwrite=True, and never writes through a symlink
save_references(paper_id, path, format?, prefer?, overwrite?) Writes the reference list to a .bib, .ris or .json (CSL-JSON) path in an allowed directory, under the same rules as save_paper

This maps directly onto bibr's auditability contract: an agent can pull a claim from get_references, then get_reference_citations to see the exact sentences and pages that cite it.

Stdio results live in server memory for the process lifetime, without a paper count cap. save_paper persists one, and load_paper brings saved exports (or bibr chew output) into a later session. Always use the returned paper_id: if different sources have the same ID, the store adds a suffix.

get_text defaults to 200 rows and caps requests at 500; offset is zero-based and page matches the exported 1-based PDF page number. get_tables(table_id=...) returns the complete table, including every page's HTML for a table continued across pages. get_figures replaces the figure image with has_image, so no image data reaches a tool response.

File access

An agent reads the papers it processes, and text in a paper can try to instruct it (prompt injection). So chew_paper, load_paper, save_paper and save_references only reach files inside the directories you allow: by default the directory bibr mcp starts in. Pass --allow-dir to choose others; it can be repeated, and ~ is expanded:

claude mcp add bibr -- uv run bibr mcp --allow-dir ~/papers --allow-dir ~/exports

Each path is checked after ~, .. and symlinks are resolved, so a symlink inside an allowed directory that points outside it is refused. A refused path fails with a tool error that names the allowed directories and this option; the server's instructions name them too, so the agent knows where to look. A --allow-dir that is not an existing directory stops the server at startup. If your client starts servers in your home directory or in / (as Claude Desktop can), the default covers that whole tree: the server logs a warning on stderr then, and you should set --allow-dir explicitly.

Behaviour change

Earlier releases let these tools read and write any file your user account could. Files outside the server's working directory now need --allow-dir.

save_paper writes only .json files and never through a symbolic link: an existing link at the target path is refused whether it points inside or outside the allowed directories, or nowhere. It replaces an existing regular file only with overwrite=True. load_paper reads only regular files of at most 256 MB, so a device or named pipe fails at once instead of exhausting memory or blocking the server.

save_references writes only .bib, .ris and .json files, in the format the extension names (a format that names another fails), under the same symlink and overwrite rules.

URL downloads (chew_url)

chew_url lets an agent go straight from a link (arXiv, publisher, data repository) to queryable extraction. Because fetching a caller-supplied URL is the canonical SSRF surface — even locally, a hostile link must not reach loopback services or a cloud VM's metadata endpoint — the download is policy-gated by bibr.utils.safe_fetch:

  • HTTPS only, port 443 only, no credentials in the URL. An internationalised host name (bücher.de) is fetched by its IDNA form (xn--bcher-kva.de).
  • Every DNS answer must be a public unicast address (private, loopback, link-local, CGNAT, multicast, IPv4-mapped and IPv4-compatible tricks, 6to4, Teredo and site-local IPv6 are refused; a NAT64 64:ff9b::/96 answer is judged by the IPv4 address it carries), and the connection is pinned to the validated IP — TLS SNI and certificate verification still use the hostname — so a DNS-rebinding race can't redirect the connection after validation.
  • The download always connects directly: HTTPS_PROXY/ALL_PROXY from the environment are ignored, because a proxy would choose the destination itself. Custom CA bundles (SSL_CERT_FILE/SSL_CERT_DIR) still apply.
  • Redirects are followed manually (bounded) and every hop re-validated, so a public URL can't 302 into an internal network or downgrade to HTTP.
  • Downloads are size-capped (100MB on the stdio server; the serve upload limit remotely) under a wall-clock deadline. Compressed transfers are refused rather than inflated: the request asks for Accept-Encoding: identity, and a server that still answers with a Content-Encoding (gzip, br, ...) fails the download, since a few hundred bytes of stacked gzip can expand to gigabytes.

The SSRF guard keeps the download off internal addresses, but any public HTTPS host is reachable, with a path and query string the agent chooses. An agent following injected instructions could therefore send text it has read (from a paper, or from a file in an allowed directory) to a server of the attacker's choosing, encoded in the URL. If that matters for your setup, limit downloads to hosts you trust (MCP_URL_ALLOWED_HOSTS=arxiv.org,zenodo.org — subdomains included) or remove the tool with MCP_CHEW_URL_ENABLED=false. Both settings apply to bibr mcp and to the serve endpoint below.

Long extractions

A PDF through OCR + LLM can take minutes, and the first call also loads models. The server streams MCP progress notifications (stage transitions, per-region OCR progress) while it works; if your client enforces a tool timeout, raise it for chew_paper (in Claude Code: MCP_TOOL_TIMEOUT=600000). One paper is processed at a time on the shared pipeline; concurrent chew_paper calls queue.

Remote MCP on bibr serve

A self-hosted deployment can expose the same tool surface over the network: set MCP_ENABLED=true (with the mcp extra installed) and bibr serve mounts a streamable-HTTP MCP endpoint at /mcp, e.g. for Claude Code:

claude mcp add --transport http bibr https://bibr.example.org/mcp \
  --header "Authorization: Bearer $AUTH_API_KEY"

It is gated by the same bearer auth as every REST route (AUTH_API_KEY; a keyless loopback server answers only loopback Host and Origin headers, see Authentication), and extraction rides the regular serve dispatch — the LitServe workers' resident models, admission control, and upload size caps all apply; no second pipeline is loaded. Concretely: chew_paper and chew_url accept files up to PIPELINE_MAX_FILE_SIZE (50 MiB), and because chew_paper carries its file base64-encoded inside the JSON-RPC body, enabling MCP raises the outer request-body cap to fit a full-size file in that form (about 68 MiB by default) — a larger body is refused with a 413 that says so. An upload counts against PIPELINE_MAX_ACTIVE_UPLOADS both while its body is received (HTTP 429) and while it is extracted (a server busy tool error), exactly like a POST /papers/extract request.

Three differences from the stdio server:

  • chew_paper takes an upload, not a path — filename plus base64 content_base64, because client paths don't exist on the server (and the server never reads its own filesystem for clients). Per-call options match POST /papers/extract: start_page/end_page, refs, consolidate, and crossref (true/false; omit to follow the server's CROSSREF_ENRICH, which is off by default). Both MCP and REST use zero-based, inclusive page indices (0 is the first page). chew_url avoids the upload entirely: the server downloads a public https:// URL itself under the SSRF policy above, capped at the serve upload limit. Operators can pin it to specific hosts with MCP_URL_ALLOWED_HOSTS or remove the tool with MCP_CHEW_URL_ENABLED=false, as on the stdio server.
  • No load_paper / save_paper / save_references — these are host-filesystem tools; use the REST API when you want the full export JSON or the reference list as a file (export_references returns the list's text).
  • Papers are per-session and bounded — each MCP client session gets its own in-memory store, capped at MCP_MAX_PAPERS_PER_SESSION (default 16; the paper chewed least recently is evicted, and re-chewing a paper counts as recent) and dropped when the session ends. A session that goes quiet for MCP_SESSION_IDLE_TIMEOUT_SECONDS (default 1800) is closed by the server and its papers are dropped, so clients that disconnect without DELETE do not pin memory. The server as a whole keeps the papers of at most 64 sessions and at most 256 MiB of export JSON: beyond either, the least recently used session's or paper's data is dropped first, however many sessions are open. A query for a dropped paper fails with unknown paper_id; re-chew it, as after a disconnect.