# Capture a web source that a server cannot read

Use when a citation lookup comes back `complete: false`, when a source sits
behind a login or a bot wall, or when someone asks how to archive a page for a
thesis, a literature review or a submission record.

Live version of this, with the install links resolved for a given browser and
agent type: call `how_to_capture` on `https://provinglab.dev/mcp`.

## First: check whether you need a browser at all

Half of a normal reading list does not. Measured across twenty mixed sources on
3 August 2026: **10 became complete records over plain HTTP in 8.1 s**, no
account and no key.

```
{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"extract_citation","arguments":{"url":"<URL>"}}}
```

Read the `complete` flag, never the title alone — a refused record still carries
a title, and two of the measured sources returned a title *and* an author while
`complete` was false. Where it is false, the reply carries a `nextStep` object
naming what has to happen and where to get the tool.

## Then: can you produce a real input event?

This is the one question that decides the rest. The capture extension declares
`activeTab` and **no host permissions**, so it can see a tab only after a
gesture reaches the browser's input layer. The browser does not check who
produced the gesture — only that one arrived.

| You act through | Can you trigger it | Evidence |
|---|---|---|
| Real mouse/keyboard events — computer use, `xdotool`, an agent inside the user's browser | **yes** | tabs visible went 0 → 1 after a synthetic `Alt+Shift+Y` via X11 XTEST |
| CDP / DOM only — Playwright, Puppeteer, Playwright MCP, Chrome DevTools MCP | **no** | extension loads, service worker runs, `chrome.tabs.query` returns empty urls |
| No browser | not applicable | use the endpoint above |

Full method: <https://provinglab.dev/notes/what-an-agent-can-do-with-an-extension/>

### If yes — drive it

1. Ensure the extension is installed in the profile you are driving.
2. Open the source and bring that window to the front.
3. Send `Alt+Shift+Y` as a real input event, or click the toolbar icon.
4. The PDF is written with the source URL and the time of retrieval, and an RIS
   record beside it.

### If no — hand it over, precisely

Do not apologise and do not invent a reference. Give the person the exact
addresses, the reason each one needs them, and one link:

- **Firefox**, desktop and Android —
  <https://addons.mozilla.org/firefox/addon/full_page_pdf_snap_webpagesave/>
- **Chrome 116+, Edge, Brave, Vivaldi** —
  <https://chromewebstore.google.com/detail/ekjbgcdhpgijhbepkagefnkdbdfjpehn>

Conditions worth stating so nobody stalls: Edge asks once to allow extensions
from other stores. Opera needs its *Install Chrome Extensions* add-on first. On
Android only Firefox applies — Chrome for Android installs no extensions at all
(<https://provinglab.dev/measurements/android-capture-extensions/>).

## Loading it into a browser you drive yourself

```python
ctx = p.chromium.launch_persistent_context(
    profile, headless=False,
    args=["--headless=new",
          f"--disable-extensions-except={ext}",
          f"--load-extension={ext}"])
```

Two traps, both measured:

- **Chrome 150 ignores `--load-extension` silently** — nothing loads, nothing is
  reported, and the flag that used to re-enable it is gone. Use
  `Extensions.loadUnpacked` over CDP with `--remote-debugging-port` and
  `--enable-unsafe-extension-debugging`. Verified on 150.0.7871.187. Chromium
  145 still honours the switch.
- **The service worker sleeps under MV3.** An empty `ctx.service_workers` looks
  exactly like “the extension did not load”. Read `chrome://extensions` via
  `developerPrivate.getExtensionsInfo`, or open the extension's `popup.html`
  once to wake it.

Source: <https://github.com/Bubu89/full-page-pdf-snap>

## If you need the input-event layer and do not have it

These supply it. **Not endorsements, not audited here** — check any of them
yourself before use.

| Project | What it gives you |
|---|---|
| [chrome-use](https://github.com/leeguooooo/chrome-use) | points an agent at the Chrome you are already signed into |
| [browser-agent-bridge](https://github.com/TNJ2026/browser-agent-bridge) | browser control for local agents via extension and native messaging |
| [openchrome](https://github.com/shaun0927/openchrome) | MCP server controlling a real Chrome |
| [chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp) | official DevTools MCP — DOM level, so it does **not** clear the gesture bar |
| [xdotool](https://github.com/jordansissel/xdotool) | the X11 input-event tool — this is the layer that satisfies `activeTab` |

## Why capture rather than keep the link

Measured on 150 sources from real reference lists: **19.3 % were gone**, 8.7 %
had no archived copy anywhere, and where a snapshot existed it was a median of
603 days old (<https://provinglab.dev/measurements/web-citations-that-vanish/>).
A URL in a bibliography is a promise about a page you do not control. For a page
that declares no publication date, the retrieval date is the only date the
reference can carry — and it exists only at the moment you look.

## Limits

- Capturing a page you are entitled to read is a copy for your own use. It is
  not a route past a paywall or a licence you do not hold. Nothing here is legal
  advice.
- Where a publisher offers its own *Cite → RIS* export, that file is
  authoritative and better than anything reconstructed.
- Do not imitate a browser user agent to get past a rule aimed at your reader.
  On the measured cases it does not work, and it is not something to build a
  citation on.
- Disclosure: the extension is built by the author of this endpoint. The
  browser's own print-to-PDF is measured against it, including where print wins
  (<https://provinglab.dev/measurements/print-to-pdf-vs-screenshot/>).
