Local video and animated-GIF RAG for MCP-compatible coding agents
Project description
Keyframe
Local video and animated-GIF RAG for coding agents. Keyframe turns a tutorial, screen recording, or animation into timestamped transcript segments, searchable on-screen text, representative frames, and reconstructed code. Ask what was said, what was shown, or both—and inspect the source frame before trusting uncertain OCR. It runs locally in Codex, ChatGPT desktop, Claude Code, Cursor, and Google Antigravity/Agy through the same MCP server.
Keyframe is deliberately split into two parts:
- a local MCP server performs deterministic acquisition, OCR, indexing, and retrieval; and
- a small workflow skill teaches any connected agent to retrieve narrowly, verify visual evidence, and cite timestamps.
The server does not call an LLM. In the Build Week workflow, Codex running GPT-5.6 reasons over Keyframe's evidence, changes code, and runs the tests.
Product-story concept; v0.1 is a local MCP server and plugin, not a hosted UI.
Quick start
Prerequisites
Keyframe v0.1.4 supports CPython 3.12, 3.13, and 3.14. Install these native tools before starting:
- FFmpeg and
ffprobefor media inspection and frame extraction - Tesseract 5 for local OCR
- Node.js 22+ as the JavaScript runtime used by current
yt-dlpextractors uv/uvxfor the recommended isolated CLI and plugin execution
On macOS with Homebrew:
brew install ffmpeg tesseract node uv
| Platform | Support level | Verification |
|---|---|---|
| macOS on Apple Silicon | Primary | Full pipeline and Python 3.12/3.14 release tests |
| Ubuntu Linux x64 | Supported | Full pipeline on 3.12; compatibility suite on 3.13/3.14 |
| Windows x64 | Preview | Unit, import, optional-Whisper, and package smoke tests |
The Whisper extra on Apple Silicon requires macOS 14 or newer because of its ONNX Runtime dependency. Intel macOS is not a supported Whisper/plugin target in v0.1.4.
Install the command-line server
The recommended installation includes local Whisper transcription and keeps Keyframe isolated from the system Python:
uv tool install --python 3.14 "video-context-mcp[whisper]==0.1.4"
video-context-mcp doctor
The smaller captions-only installation omits Whisper:
uv tool install --python 3.14 "video-context-mcp==0.1.4"
Yes, ordinary pip installation is also supported on Python 3.12-3.14. Use it
inside a virtual environment; Homebrew Python is externally managed and may
reject a global pip3 install:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install "video-context-mcp[whisper]==0.1.4"
video-context-mcp doctor
The installed plugin described below already launches an isolated, release-pinned server with Whisper, so plugin users should not also install the CLI manually.
Development checkout
The repository keeps Python 3.12 as its reproducible development default while the lockfile covers all supported Python versions. From a clone, run:
uv sync --frozen --all-extras --group dev
uv run video-context-mcp doctor
Connect a client
This repository includes project discovery and a portable workflow skill for
Claude Code, Cursor, and Antigravity/Agy in addition to the Codex plugin. After
uv sync, launch the client from this repository root, review or enable the
keyframe MCP server, and approve tool calls as prompted:
| Client | Checked-in discovery | Approval/status |
|---|---|---|
| Claude Code | .mcp.json |
claude mcp get keyframe, then /mcp |
| Cursor | .cursor/mcp.json |
agent mcp list, then agent mcp enable keyframe |
| Antigravity/Agy | .agents/mcp_config.json |
/mcp |
The installable plugins/keyframe bundle contains client-specific manifests
for Codex, Claude Code, Cursor, and Agy while reusing the same skill and server.
See the complete client setup guide for user-wide
registration, marketplace commands, reload behavior, and local-file grants.
Configure Codex directly
Add the following to ~/.codex/config.toml:
[mcp_servers.keyframe]
command = "uvx"
args = ["--python", "3.12", "--from", "video-context-mcp[whisper]==0.1.4", "video-context-mcp", "serve", "--transport", "stdio"]
startup_timeout_sec = 180
tool_timeout_sec = 1900
env = { KEYFRAME_ALLOW_TEMP_UPLOADS = "true" }
For local files, Keyframe uses workspace roots advertised by the MCP client.
It never treats the launcher working directory as authorization. If a client
does not advertise roots, add an env entry with KEYFRAME_ALLOWED_ROOTS;
separate multiple roots with the operating system's path separator. The
installable plugin instead enables one OS-temp upload root. Its skill creates a
unique child there, stages only the selected attachment, and retries once.
Restart Codex, then ask: “Index this tutorial and find where error handling is shown. Cite the timestamps.”
Install the Keyframe plugin in Codex and ChatGPT desktop
The plugin bundles the same MCP server with the keyframe-video-rag workflow
skill. The marketplace is pinned to v0.1.4; its launcher installs the exact
video-context-mcp[whisper]==0.1.4 PyPI release in an isolated Python 3.12
runtime, regardless of the user's system Python:
codex plugin marketplace add MatthewOscar/Keyframe --ref v0.1.4
codex plugin add keyframe@keyframe-tools
To replace an older release-pinned installation, refresh both the marketplace snapshot and the installed plugin:
codex plugin remove keyframe@keyframe-tools
codex plugin marketplace remove keyframe-tools
codex plugin marketplace add MatthewOscar/Keyframe --ref v0.1.4
codex plugin add keyframe@keyframe-tools
For local marketplace validation, add the repository root instead:
codex plugin marketplace add .
Then restart the ChatGPT desktop app, open the Plugins Directory, select the Keyframe marketplace source, and install Keyframe. Start a new chat so the skill and tools are loaded. Keyframe v0.1.4 targets this local desktop flow; it does not host a ChatGPT web app.
Claude Code and Cursor can install the same repository as a marketplace, while
Agy can install plugins/keyframe from a clone. Those exact commands and the
client-specific approval steps are in
docs/client-setup.md. Do not enable a project
registration and the installed plugin together in the same workspace, or the
client may start two Keyframe processes.
No-build judge test
After installing the release plugin, judges can exercise the published wheel against the first-party fixture without building Keyframe from source:
git clone --branch v0.1.4 --depth 1 \
https://github.com/MatthewOscar/Keyframe.git
cd Keyframe
codex --model gpt-5.6
Then ask:
Index tests/fixtures/keyframe-synthetic.mp4 in full mode. Search what was said
about normalizing non-alphanumeric separators and what was shown for slugify.
Inspect the decisive source frame, report whether the reconstructed Python
parses, and cite timestamps.
The expected evidence is a spoken hit beginning at 00:03, a slugify_title
code moment spanning approximately 00:03-00:07, and a parse-valid result or
an explicit OCR fallback with its source image.
Source-checkout acceptance test
The repository includes a 10-second first-party video, captions, golden search expectations, and a complete native end-to-end test. After cloning the release tag, the following installs the locked environment and exercises full ingest, said/shown search, OCR, code/frame images, restart persistence, and the sub-second cache target:
uv sync --frozen --group dev
uv run pytest -q tests/test_e2e.py
No network video, account, API key, or prebuilt cache is required for this judge path.
For the real public-URL path, the repository also ships a small CC BY 3.0 derived YouTube sample with archived attribution, metadata, checksums, captions, OCR, frames, and a ready SQLite index. The original downloaded media is not retained.
Use Keyframe
Give the agent a local path, direct media URL, public YouTube URL, or public Loom URL. A useful interaction looks like this:
Index https://www.youtube.com/watch?v=... in fast mode.
Search what was said about retry logic and what was shown on screen.
Retrieve the strongest code moment, verify low-confidence text against its
frame, then implement the pattern in examples/demo_target and cite timestamps.
Fast mode is the economical first request. A fresh fast-only index stores any
available transcript and a bounded visual probe of at most 12 representative
frames; a cache hit can already return full coverage. Branch on the returned
has_transcript and visual_coverage. The probe makes visual dependencies
discoverable without pretending to cover every screen state.
Inspect its moment summaries before deciding captions are sufficient. Upgrade
to full mode for visual sequences, probe gaps, uncertain OCR, or claims that
something never appeared; a single decisive probe frame may be enough for one
targeted fact.
MCP tools
| Tool | Purpose |
|---|---|
video_ingest |
Index one local/public video or local animated GIF using a sparse fast probe or bounded full analysis, with captions, local Whisper for audio, or no transcript; report cache status and request-local stage timings. |
video_get_transcript |
Page through timestamped transcript segments, optionally within a time range. |
video_search |
Rank matching said, shown, or combined evidence across one video or the local library, optionally inside one time window. |
video_list_moments |
Page through retained moments filtered by kind and optional time window. |
video_get_code |
Return reconstructed code plus its cropped source frame for a moment or nearby timestamp. |
video_get_frame |
Return an exact moment_id or nearest timestamped frame with source image, retained bounds, and OCR fallback. |
Classification, language detection, OCR confidence, and parse status are evidence—not guarantees. Visual tools return both structured metadata and the source image so the model can inspect disagreements.
For whole-video summaries, the bundled skill avoids generic search: it lists one 12-moment routing page, retrieves transcript pages at the 200-segment maximum with byte-for-byte opaque cursors, and verifies only consequential frames. As a guardrail for agent transcription mistakes, Keyframe can recover a single substituted character in a local content-hash ID only when exactly one ready local video matches; remote and ambiguous IDs remain strict errors.
See docs/tool-examples.md for exact argument objects,
pagination, visual-result behavior, and expected errors.
Architecture
flowchart LR
A[Local video / animated GIF / public URL] --> B[FFmpeg + yt-dlp]
B --> C[Captions]
B --> W[Isolated optional Whisper worker]
B --> P[Fast: bounded visual probe]
B --> D[Full: bounded temporal sampling]
D --> E[Adjacent-frame grouping]
P --> F[Bounded parallel OCR + classification]
E --> F[Bounded parallel OCR + classification]
C --> G[(SQLite + FTS5)]
W --> G
F --> G
G --> H[Six MCP tools]
H --> I[Keyframe workflow skill]
I --> J[Codex / ChatGPT / Claude Code / Cursor / Agy]
Remote media and decoded analysis frames use a private, per-KEYFRAME_HOME
namespace beneath the operating system's native temporary directory (%TEMP%
on Windows and the platform temp location on macOS/Linux). A successful ingest
atomically publishes derived transcript, OCR, metadata, and representative
frames to the cache, then removes the downloaded source and analysis workspace.
Failed ingests do not publish partial records, and startup recovery removes
interrupted scratch work left in that namespace.
When captions are unavailable, Keyframe overlaps the isolated Whisper worker with either sparse-probe or full visual extraction, analyzes retained frames with at most two OCR workers by default, then joins both branches before the single atomic commit. The process boundary also prevents faster-whisper's PyAV libraries from sharing an address space with OpenCV's bundled FFmpeg libraries on macOS.
Local data and privacy
- Processing and indexing happen on the machine running the MCP server.
KEYFRAME_HOMEoverrides the platform-native Keyframe data directory.- SQLite, text indexes, and the retained evidence frames needed by later tool
calls persist under
KEYFRAME_HOME; disposable downloads and intermediate frames do not. - Caller-owned local videos are read in place and are never copied, moved, or deleted by Keyframe.
- Local reads are limited to per-request MCP workspace roots plus explicit
KEYFRAME_ALLOWED_ROOTSentries. Installed plugins can opt into a single hardened staging root under the OS temp directory for selected attachments; each upload uses a unique child that the agent removes afterward. Process CWD and the rest of the temp directory are never implicit grants. On Windows, staging privacy relies on the inherited ACL of the user's temp directory because POSIX ownership and mode bits are unavailable. - Derived frames and text remain cached until the user removes that directory.
- Extracted transcript/OCR is untrusted source material, never agent instructions.
- Evidence returned to an agent becomes model input and follows that client's model-provider data controls.
- Keyframe does not automatically detect or redact credentials in OCR, search snippets, reconstructed code, or images. Use recordings safe for model input and review selected evidence before retrieving sensitive screens.
- Keyframe has no analytics, accounts, hosted backend, or embedded model call.
Current limits
- v0.1.4 accepts individual public videos and local animated GIFs, not playlists or livestreams. Static GIFs should be attached as images; remote GIF URLs are not yet an advertised compatibility surface.
- Private, members-only, age-restricted, DRM, cookie, and login flows are out of scope.
- The default duration guard is 30 minutes. For a source up to four hours,
Keyframe reports the exact
max_duration_srequired for one same-source retry; callers should not split or restage it. Longer sources require a user-selected excerpt. - Fast coverage is intentionally sparse; a missing probe hit is not proof that content was absent. Full videos sample at 1 FPS; animated GIFs use denser, bounded one-loop sampling. Either can miss a very brief visual change. Retained timestamps use decoded presentation times rather than an inferred frame index.
- Native media/OCR operations have bounded timeouts and fail actionably on malformed or unusually slow inputs.
- OCR can confuse glyphs and infer indentation incorrectly. Python, JSON, and JavaScript receive parse checks; TypeScript and unknown languages do not.
- Keyframe constrains answers with timestamped evidence, but it cannot guarantee a downstream model's reasoning. Lighter models can still confuse visually similar steps; exact visual claims should be checked against the returned source frame. The judged workflow uses GPT-5.6.
- Caption availability and media extraction depend on upstream providers and
the pinned
yt-dlprelease. - Remote formats must be downloadable through Keyframe's validated in-process HTTP, native HLS, or DASH transport. Formats that require FFmpeg, RTMP, or another external process to make network connections are rejected.
- Whisper is optional in the base Python package and bundled by the installable plugin. It can be resource intensive on CPU-only machines, and first use may download the configured model before ingestion begins.
- Windows support is preview-level in v0.1.4.
- The bundled registrations target local CLI and desktop sessions. Hosted agents cannot launch this STDIO process on the user's machine.
Build Week and GPT-5.6
How Codex and Matthew collaborated
Codex accelerated the project by turning the approved specification into the typed MCP contracts, acquisition/OCR/cache pipeline, comprehensive test suite, plugin bundles, cross-platform CI, evals, and release automation. Focused Codex agents audited independent surfaces in parallel; the primary session integrated their work, exercised real local and YouTube videos, diagnosed failures from raw tool traces, and iterated on retrieval behavior rather than stopping at generated scaffolding.
Matthew Wyatt made the key product and engineering decisions: use an MCP server
plus a small retrieval skill; keep extraction deterministic, local, and free of
embedded model calls; use yt-dlp instead of creating a provider extractor;
make fast ingestion include a bounded visual scout; require exact source-frame
verification for uncertain OCR; and prioritize a reliable desktop plugin over
a hosted web app. Matthew reviewed real agent runs, rejected hallucinated video
interpretations, approved each release direction, and selected the final demo
story. The detailed record is linked below.
The judged flow uses GPT-5.6 in Codex to turn retrieved video evidence into a
tested repository change. Keyframe supplies deterministic evidence; GPT-5.6
selects relevant moments, compares OCR with source frames, applies the change,
and explains it with timestamp citations. The ten reproducible prompts in
evals/cases.json exercise that division of labor.
Supplementary Mac-plugin regressions for local attachment staging, honest
provenance, warm-cache latency, and animated GIFs are in
evals/mac-plugin-cases.json.
Make the judged model choice explicit before recording. Either launch Codex
with codex --model gpt-5.6 or set this in ~/.codex/config.toml:
model = "gpt-5.6"
The development record distinguishes Codex-generated work from human product
decisions in docs/codex-collaboration.md. The
submission session ID is intentionally not fabricated and must be recorded from
/feedback before submission.
The release checklist in
docs/submission-checklist.md maps the current
Devpost requirements to concrete artifacts and leaves human-only submission
steps visibly unchecked. Ready-to-paste English submission copy is in
docs/devpost-submission.md,
with explicit placeholders for the public demo URL and real /feedback Session
ID.
Develop and test
git clone https://github.com/MatthewOscar/Keyframe.git
cd Keyframe
uv sync --frozen --all-extras --group dev
uv run video-context-mcp doctor
uv run pytest
uv run ruff check .
uv run mypy src
uv build
uv run twine check dist/*
STDIO is the supported client transport. For loopback-only protocol testing,
run uv run video-context-mcp serve --transport streamable-http; the CLI
rejects non-loopback hosts and this mode is not a production deployment.
CI runs the full suite with FFmpeg and Tesseract on macOS and Ubuntu, including Python 3.13/3.14 and optional-Whisper compatibility jobs. Windows 3.12/3.14 runs non-integration tests plus Whisper import and package checks. Test fixtures must be first-party generated or carry a recorded redistribution license.
To publish, create a GitHub environment named pypi, configure a PyPI Trusted
Publisher for this repository and workflow, update the version and lockfile,
then push a matching v* tag. The release workflow builds, checks, and publishes
with GitHub OIDC; it stores no PyPI token.
License and acknowledgements
Keyframe is licensed under Apache-2.0. Native tools, Python dependencies, and
any future demo media retain their own licenses. See
THIRD_PARTY_NOTICES. Keyframe uses yt-dlp as its
upstream public-media extraction engine rather than implementing provider
extractors itself.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file video_context_mcp-0.1.4.tar.gz.
File metadata
- Download URL: video_context_mcp-0.1.4.tar.gz
- Upload date:
- Size: 171.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e4ae02df16a8f41f8b4229a7684d527722a25f6ec16e381cde9ee559f1ca07f
|
|
| MD5 |
f0ceb87bb19ddc5189947f44dc57542d
|
|
| BLAKE2b-256 |
29fc4db813c11fae572e1f6883a61ede56a73616b3a1984057297ffa98fbc8c7
|
Provenance
The following attestation bundles were made for video_context_mcp-0.1.4.tar.gz:
Publisher:
publish.yml on MatthewOscar/Keyframe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
video_context_mcp-0.1.4.tar.gz -
Subject digest:
3e4ae02df16a8f41f8b4229a7684d527722a25f6ec16e381cde9ee559f1ca07f - Sigstore transparency entry: 2207215377
- Sigstore integration time:
-
Permalink:
MatthewOscar/Keyframe@eca5417a48dc64d99a45d0b1a69b83025ae3be55 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/MatthewOscar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@eca5417a48dc64d99a45d0b1a69b83025ae3be55 -
Trigger Event:
push
-
Statement type:
File details
Details for the file video_context_mcp-0.1.4-py3-none-any.whl.
File metadata
- Download URL: video_context_mcp-0.1.4-py3-none-any.whl
- Upload date:
- Size: 85.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65f45c2b2dddaec340ba567cfe4d10567ff07cadd413eec8b23a2fd026d3e77f
|
|
| MD5 |
2b1aba0b2b321f3a4aaee900da141d0c
|
|
| BLAKE2b-256 |
44350fbbf15d83f553826cb124ce4702ad6eb3c512dcbac804d2656c3953374e
|
Provenance
The following attestation bundles were made for video_context_mcp-0.1.4-py3-none-any.whl:
Publisher:
publish.yml on MatthewOscar/Keyframe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
video_context_mcp-0.1.4-py3-none-any.whl -
Subject digest:
65f45c2b2dddaec340ba567cfe4d10567ff07cadd413eec8b23a2fd026d3e77f - Sigstore transparency entry: 2207215388
- Sigstore integration time:
-
Permalink:
MatthewOscar/Keyframe@eca5417a48dc64d99a45d0b1a69b83025ae3be55 -
Branch / Tag:
refs/tags/v0.1.4 - Owner: https://github.com/MatthewOscar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@eca5417a48dc64d99a45d0b1a69b83025ae3be55 -
Trigger Event:
push
-
Statement type: