Proving Lab · Recipes

Recipes: turn a web source into a citation

Short, complete instructions for the citation endpoint at /mcp — in a terminal, in WSL, in Python, and in AI tools that speak MCP. Every one of them was run on 3 August 2026 before it was written down; an untested recipe is a claim.

No account, no key, no rate limit · what the endpoint is and what it refuses

What you get back

For a page that is a work: authors, title, journal, year, volume, pages, DOI, ISSN and licence — plus a ready-to-import RIS record and a BibTeX entry. For a page that is a paywall, an error or a bot check: complete: false and a warning naming the wall. Test complete before you file the result — a refused record still carries a title, and it will read like a work.

A reading list becomes a .ris file

The recipe most people actually want. One URL per line in reading-list.txt, one importable file out. Sources that cannot be read are named on stderr and left out of the file rather than half-imported.

while read -r u; do
  curl -sX POST https://provinglab.dev/mcp \
    -H 'content-type: application/json' \
    -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",
         \"params\":{\"name\":\"extract_citation\",\"arguments\":{\"url\":\"$u\"}}}" \
  | python3 -c 'import json,sys
d = json.loads(json.load(sys.stdin)["result"]["content"][0]["text"])
sys.stdout.write(d["ris"]) if d.get("complete") else \
  sys.stderr.write("skipped: " + d.get("warning","") + "\n")'
done < reading-list.txt > literature.ris

Then Zotero → File → Import, or Citavi → Import → RIS. Measured on three scholarly URLs: three records, under two seconds, imported without editing.

Claude Code, in one line

claude mcp add --transport http provinglab https://provinglab.dev/mcp

claude mcp list then reports ✔ Connected. After that you can simply say: "cite these four links for my bibliography" — the tool is called for each one, and the ones behind a wall are reported as such instead of invented.

Claude Desktop and other MCP clients

Add https://provinglab.dev/mcp as a remote MCP server (transport: streamable HTTP). Authentication is offered but not required; anonymous requests get identical answers. In clients that only accept local servers, the usual bridge works:

{
  "mcpServers": {
    "provinglab": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://provinglab.dev/mcp"]
    }
  }
}

Python — mind the user agent

The standard library identifies itself as Python-urllib, and the CDN in front of this site answers that with HTTP 403 before the worker ever sees the request. Any user agent of your own is enough. This is not a rule against automation — it is a filter that does not know the difference.

import json, urllib.request

def cite(url):
    body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/call",
        "params": {"name": "extract_citation", "arguments": {"url": url}}}).encode()
    req = urllib.request.Request("https://provinglab.dev/mcp", body, {
        "content-type": "application/json",
        "user-agent": "my-bibliography-script/1.0",   # <- without this: 403
    })
    answer = json.loads(urllib.request.urlopen(req, timeout=60).read())
    return json.loads(answer["result"]["content"][0]["text"])

record = cite("https://doi.org/10.1038/s41586-020-2649-2")
if record.get("complete"):
    print(record["ris"])
else:
    print("not usable:", record["warning"])

WSL: from the browser into the terminal

The two halves of the work sit on different sides of the filesystem boundary. A source behind a university login can only be captured in the browser, and the file then has to be found from a shell.

In Full Page PDF Snap, switch on Copy file path after saving and set the format to WSL under Settings. After a capture the path is on the clipboard in the shape a Linux shell understands:

/mnt/c/Users/<you>/Downloads/Full Page PDF Snap/pubmed_2026-08-03_0911_0001.pdf

Paste it straight after a command, or into a chat with an AI tool that can read files. The RIS record for the same capture sits next to the PDF with the same name and a .ris extension.

Which of the two routes for which source

The endpointThe extension
Runson a server, anonymousin your browser, logged in
Gives youthe referencethe reference and the document
Behind a loginnoyes
Cost per sourcenone, scriptableone click
OutputRIS + BibTeXPDF with the fields inside, plus RIS

So the division is not a compromise: the endpoint for volume, the extension for the ones it refuses. Both emit the same RIS format, so everything lands in one Zotero or Citavi library regardless of the route. The refusal list from the first pass tells you which sources need the second.

For an agent rather than a person

These recipes are also published as a machine-readable skill, alongside the measurement methods:

What none of this proves

A citation record says what a page declares about itself. It is not a check that the work exists, that the DOI resolves to it, or that the page is honest — for the eight of eighteen platforms where the data is thin, that matters. A screen capture, likewise, is a picture of a screen and not a qualified electronic document. Where the content decides something, read the source.