Skip to main content
Glama
README.md
<div align="center">

# 🎞️ broll-mcp

**Find, preview and cut public-domain b-roll from the Internet Archive, from inside any MCP agent.**

Every clip lands on disk with a provenance record next to it: source, license, exact segment and attribution text.

[![CI](https://github.com/edigeh/broll-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/edigeh/broll-mcp/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-3776AB?logo=python&logoColor=white)
![MCP](https://img.shields.io/badge/MCP-stdio-8A2BE2)
![No API key](https://img.shields.io/badge/API%20key-not%20needed-success)

</div>

---

```text
You:    "I need 8 seconds of children eating bananas, ideally black and white."

Agent β†’ search_footage("children eating bananas")       5 ranked items, license + tier on each
Agent β†’ inspect_item("AboutBan1935")                    scenes with a keyframe image per scene
Agent β†’ extract_clip("AboutBan1935", start_s=300, 8s)   verified H.264 MP4 + provenance JSON
Agent β†’ get_provenance(clip_id)                         "About Bananas" (1935), Prelinger Archives
                                                        via Internet Archive Β· Public Domain
```

The agent sees the footage before it cuts: `inspect_item` returns real keyframe images, so a vision model can pick the shot instead of guessing from titles.

## Why

archive.org holds decades of public-domain film (Prelinger, NASA, newsreels, stock footage), but using it from an agent is awkward:

- Raw item metadata can exceed **1 MB** for one item. That's a huge token cost before you've seen a single frame.
- Licenses differ **per item**, even inside one collection.
- Clips cut with `-c copy` snap to keyframes. An 8 s request came back as **11.35 s**.

broll-mcp handles all of this behind four small tools. Responses are compact and cached, and the server applies polite rate limits. Each cut is re-encoded and checked against the requested length.

## Features

| | |
|---|---|
| πŸ”Ž **Brief-driven search** | Plain-language briefs become archive.org queries. A two-stage search first requires every term, then matches any term. Local ranking adds collection trust tiers and a black-and-white preference. Results come back 5 per page. |
| πŸ–ΌοΈ **Visual inspection** | Scene detection runs over HTTPS (PySceneDetect, with ffmpeg as fallback) and returns one keyframe per scene as an MCP image. Items that hold hundreds of clips return a paged clip list with thumbnails. |
| βœ‚οΈ **Frame-accurate cuts** | Cuts are re-encoded to H.264 MP4 with faststart. ffprobe confirms the duration is within Β±0.1 s and the decode shows zero errors, and you get 3 check frames back. The same request twice returns the same clip. |
| πŸ“œ **Provenance by design** | A JSON record sits next to every clip. It holds the source file MD5, the license copied verbatim, `commercial_use`, the data node, a timestamp and ready-to-paste attribution. Provenance is written before the clip, so a clip never exists without its record. |
| 🀝 **Polite to archive.org** | Follows [archive.org's bot guidance](https://archive.org/developers/bots.html): a descriptive User-Agent, a 1 req/s token bucket, at most 4 media connections, `Retry-After` support and exponential backoff. |
| πŸ’Ύ **Cache-first** | Uses a SQLite WAL cache. Repeating a search, inspection or cut sends **0** upstream requests, and every tool result reports `upstream_requests`. |

## Quick start

**Requirements:** Python 3.12+, [uv](https://docs.astral.sh/uv/), and `ffmpeg` / `ffprobe` built with HTTPS support (`brew install ffmpeg`, `apt install ffmpeg`).

### Claude Code

```bash
claude mcp add broll -- uvx --from git+https://github.com/edigeh/broll-mcp broll-mcp
```

### Claude Desktop, Cursor and other MCP hosts

```json
{
  "mcpServers": {
    "broll": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/edigeh/broll-mcp", "broll-mcp"],
      "env": { "BROLL_AGENT_MODEL": "claude-opus-5-5" }
    }
  }
}
```

### From a clone

```bash
git clone https://github.com/edigeh/broll-mcp && cd broll-mcp
uv sync
uv run broll-mcp          # stdio server; point your host at this command
```

Try it without an agent using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector):

```bash
npx @modelcontextprotocol/inspector --cli uv run broll-mcp \
  --method tools/call --tool-name search_footage --tool-arg 'brief=aerial highway 1950s'
```

## Tools

| Tool | What it does | Upstream cost |
|---|---|---|
| `search_footage(brief, filters?, cursor?)` | Ranked items with title, year, collections, trust tier, license, runtime, tile image URL and a short *why it matched*. Filters: `max_tier`, `license` (`stated` Β· `commercial_ok` Β· `any`), `collections`, `year_from/to`, `color`. | ≀ 2 requests, 0 when cached |
| `inspect_item(identifier, file?, window?, page_size?, cursor?)` | **Scene mode:** scenes with timings plus a keyframe image each, a shotlist excerpt and license. **Clip-list mode:** for items with many separate clips, a paged list with thumbnails. | a few requests, 0 when cached |
| `extract_clip(identifier, file, start_s, duration_s, label?, audio?, brief?)` | Cuts 1–60 s into `BROLL_HOME/clips/`, verifies it and writes the provenance JSON. Returns 3 check frames. | ~3 requests, 0 on repeat |
| `get_provenance(clip_id \| identifier, cursor?)` | Reads the record for a clip, or for an item and the clips already cut from it. Local only. | always 0 |

**Resource:** `broll://stats` reports request counts by endpoint, cache contents, the active scene detector and the ffmpeg version.

Every tool declares an `outputSchema` and returns `structuredContent`. Errors come back as tool results with a stable code, such as `NO_RESULTS`, `ITEM_NOT_FOUND`, `FILE_NOT_IN_ITEM`, `RANGE_OUT_OF_BOUNDS`, `THROTTLED` or `CUT_VERIFY_FAILED`. Each message tells the agent how to recover.

## Provenance record

```jsonc
// clips/AboutBan1935__0300.00-0308.00__bananas.provenance.json
{
  "schema": "broll-mcp/provenance/1",
  "clip_id": "clp_20261023_4ee00c",
  "clip_sha256": "78e33da7…",
  "source": {
    "identifier": "AboutBan1935",
    "title": "About Bananas",
    "file": "AboutBan1935.mp4",
    "file_md5": "de484b1b573d28be4860f00a593f4416",
    "derived_from": "AboutBan1935.mpeg",
    "collections": ["prelinger"],
    "tier": 1,
    "date": "1935",
    "creator": "Castle Films"
  },
  "license": {
    "licenseurl": "http://creativecommons.org/licenses/publicdomain/",
    "license_label": "Public Domain (CC PD dedication)",
    "commercial_use": "allowed",
    "attribution_required": "no",
    "determined_from": "licenseurl"
  },
  "segment": { "start_s": 300.0, "duration_s": 8.0, "verified_duration_s": 7.975 },
  "retrieval": { "retrieved_at": "2026-10-23T12:28:30Z", "data_node": "dn600301.us.archive.org", "user_agent": "broll-mcp/0.1.0 (…)" },
  "processing": { "ffmpeg": "8.1.1", "audio": false, "reencoded": true },
  "attribution_text": "\"About Bananas\" (1935), Prelinger Archives via Internet Archive, https://archive.org/details/AboutBan1935. License: Public Domain (CC PD dedication)."
}
```

License labels come only from an exact URL table covering PD, CC0, the PD Mark, and CC BY / BY-SA / BY-ND / BY-NC variants for versions 2.0–4.0. Any value the table doesn't recognize is reported as `unknown`, never guessed.

## Configuration

| Variable | Default | Purpose |
|---|---|---|
| `BROLL_HOME` | platform data dir (`~/Library/Application Support/broll-mcp`, `~/.local/share/broll-mcp`) | Cache, keyframes, clips. The server writes nowhere else. |
| `BROLL_AGENT_MODEL` | `unspecified` | Model name sent in the User-Agent, as archive.org asks of AI agents. |
| `BROLL_CACHE_MAX_MB` | `2048` | Keyframe cache budget; older keyframes are evicted first. |

Collection trust tiers live in `BROLL_HOME/tiers.toml`. Tier 1 is `prelinger` and `nasa`, tier 2 is `stock_footage` and `feature_films`, and everything else is tier 3. Edit the file to tune ranking and the `max_tier` filter.

## How it works

```text
 MCP host ──stdio──► server.py ── jsonschema validation, result shaping, images
                        β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   SearchService   SceneService        ClipService          SQLite cache (WAL)
   two-stage query  PySceneDetect /     cut β†’ ffprobe verify  searches Β· items Β· scenes
   + local ranking  ffmpeg, keyframes   β†’ provenance β†’ clip   clips Β· provenance Β· request_log
        └───────────────┴─────── ArchiveClient β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     UA Β· 1 req/s bucket Β· 4 media slots Β· backoff
                                    β”‚
                     archive.org (advancedsearch Β· metadata Β· download)
```

[`ARCHITECTURE.md`](ARCHITECTURE.md) has the full design, built on evidence (E1–E16) from live archive.org responses. Raw captures are in [`research/`](research/).

## Development

```bash
uv sync
uv run pytest                 # 76 offline tests (mock transport, captured metadata)
uv run ruff check . && uv run ruff format --check .
uv run python scripts/e2e_smoke.py /tmp/broll-e2e   # live end-to-end over stdio against archive.org
```

See [CONTRIBUTING.md](CONTRIBUTING.md).

## A note on rights

broll-mcp copies license information exactly as archive.org publishes it, and it makes no legal judgement. `commercial_use: "unknown"` means archive.org states no machine-readable license for that item. Check the source item before you publish footage, especially material that carries only a rights statement.

## License

[MIT](LICENSE) for the code. Footage belongs to its respective rights holders under the terms recorded in each clip's provenance file.

TDQS

A4.1/5.0

Scored across 4 tools

Disambiguation4/5

Each tool maps to a distinct stage: search remote footage, inspect a remote item, extract a clip locally, and read local provenance. There is minor overlap because both inspect_item and get_provenance can return license/source information for an item, but the remote-vs-local distinction is clearly stated.

Naming Consistency5/5

All four names follow a consistent snake_case verb_noun pattern: search_footage, inspect_item, extract_clip, get_provenance. The convention is predictable and immediately readable.

Tool Count5/5

Four tools are well-scoped for a focused footage search, inspection, extraction, and provenance workflow. Each tool has a clear role and none appears redundant.

Completeness4/5

The surface covers the core lifecycle from discovery through extraction and provenance, with paging and repeat-cut handling described. Minor gaps exist around managing or listing already extracted clips, but agents can work around this through provenance lookups.

Maintenance

ActivityMaintained
ResponsivenessNo issues