MCP server#
The vid2context MCP server lets an agent (Claude Code, Claude Desktop, Cursor, VS Code, any stdio MCP client) read videos: transcripts, outlines, single chapters, search across what it has read, collections, and prompts for writing cited briefs. No API key is needed for YouTube captions; keys in .env only add speech-to-text and LLM rungs.
The remote transport for ChatGPT and Claude.ai (Streamable HTTP with auth) is packages/mcp-http, which serves the same server.
Setup#
Node 22 or newer, then npm install in the repo.
Claude Code#
From the repo root:
claude mcp add vid2context -- npx tsx packages/mcp/src/bin.tsTo use it from any folder, give the absolute path, and add -s user to make it available in every project:
claude mcp add -s user vid2context -- npx tsx /path/to/vid2context/packages/mcp/src/bin.tsCheck it with claude mcp list, or /mcp inside a session.
Other stdio clients#
Claude Desktop (claude_desktop_config.json), Cursor (.cursor/mcp.json) and VS Code (.vscode/mcp.json, key servers instead of mcpServers) take the same command:
{
"mcpServers": {
"vid2context": {
"command": "npx",
"args": ["tsx", "/path/to/vid2context/packages/mcp/src/bin.ts"]
}
}
}Keys go in the repo's .env (see .env.example); the server loads it on start. Logs go to stderr, so stdout stays clean for the protocol.
Inspect it#
npx @modelcontextprotocol/inspector --cli npx tsx packages/mcp/src/bin.ts --method tools/list
npx @modelcontextprotocol/inspector --cli npx tsx packages/mcp/src/bin.ts --method tools/call \
--tool-name get_outline --tool-arg url=https://www.youtube.com/watch?v=jNQXAC9IVRwWithout --cli the Inspector opens its web UI.
Reading strategy: outline first#
Speech is about 12k tokens per hour, so a 15-episode series of 30-minute videos is about 90k tokens. Read only what you need:
get_outlineshows the chapters (or sections of about five minutes when the video has none), each with its start time, a timestamp link and its token count.get_sectionreads one chapter by index or title, or a time range.search_transcriptfinds where a topic comes up across every video read so far, a collection, or given URLs.get_transcriptreturns everything; withmaxTokensit pages, and each page ends with a cursor for the next one.
For many videos, put them in a collection (create_collection, add_to_collection), check get_collection for the token total, then either export_context_pack (when it fits) or outline-first reading.
Every video any tool reads is saved, so later calls are instant and search covers it. With no storage provider configured, the library lives in memory for the life of the server process.
Citations#
Results carry [m:ss] timestamps and, for YouTube, timestamp links (https://www.youtube.com/watch?v=ID&t=SECONDSs). The brief and buyer_guide prompts ask the agent to cite every claim like this:
The cool thing about the elephants, he says, is their really long trunks [Me at the zoo @ 0:05](https://www.youtube.com/watch?v=jNQXAC9IVRw&t=5s).Tools#
Arguments shown as JSON. A video input is always url (a URL, a YouTube id or a local file path). Failures are tool results with isError: true and a JSON body: { "error": "<code>", "message": "...", "hint": "<how to recover>", "attempts": [...] }.
Defined by this package#
| Tool | Example call | Returns |
|---|---|---|
get_transcript |
{ "url": "https://youtu.be/jNQXAC9IVRw" } |
Header (title, channel, URL, duration, transcript origin, chapters) plus [m:ss] lines |
{ "url": "...", "maxTokens": 4000 }, then { "url": "...", "maxTokens": 4000, "cursor": "seg:412" } |
One page at a time, ending with next_cursor: seg:N until [End of transcript: ...] |
|
get_metadata |
{ "url": "jNQXAC9IVRw" } |
Title, channel, description, duration, chapters, tags, language |
list_formats |
{} |
text, json, markdown, srt, vtt, pack, ... with descriptions |
get_outline |
{ "url": "https://www.youtube.com/watch?v=cCPsdFdlshc" } |
Chapters or 5-minute sections with start, tokens and url, plus totalTokens |
get_section |
{ "url": "...", "chapter": 1 }, { "url": "...", "chapter": "engines" }, { "url": "...", "from": "12:00", "to": "17:30" } |
That part of the transcript with the header |
list_items |
{ "url": "https://www.youtube.com/playlist?list=PL...", "limit": 20 } |
The videos in a playlist, channel, feed or page |
get_comments |
{ "url": "...", "limit": 20 } |
Top comments; pinned and creator comments flagged |
export_context_pack |
{ "collection": "g-wagon", "maxTokens": 60000 } |
One Markdown document for all the videos, trimmed to the budget |
list_models |
{ "kind": "llm" } |
Rungs with tier, quality, capabilities, enabled, and the env var names that enable them (never values) |
Wired from other packages#
| Tool | Example call | Returns |
|---|---|---|
search_transcript |
{ "query": "timing chain", "collection": "g-wagon-1a2b3c4d" }, { "query": "rust", "urls": ["..."] } |
Hits with title, [m:ss], the text and a timestamp link |
build_brief |
{ "collection": "g-wagon-1a2b3c4d", "question": "Which years to avoid?" } |
A server-side cited brief made on a free LLM rung (when the collection is too big for your context) |
extract |
{ "template": "buyer-guide", "collection": "g-wagon-1a2b3c4d" } |
Structured JSON with citations |
list_collections |
{} |
Collections with counts |
create_collection |
{ "name": "G-Wagon", "sources": ["https://www.youtube.com/@alliancemotorworks"] } |
The collection (sources are followed) |
add_to_collection |
{ "collection": "g-wagon-1a2b3c4d", "urls": ["https://youtu.be/..."] } |
The updated collection and what was added, skipped or failed |
get_collection |
{ "collection": "g-wagon-1a2b3c4d" } |
The collection with video summaries |
ask |
{ "question": "Which years have the most engine problems?", "collection": "g-wagon-1a2b3c4d" } |
A server-side answer with [n] citations (needs an LLM rung) |
research_videos |
{ "urls": ["...", "..."], "query": "maintenance costs" } |
Ranked excerpts per video with context |
list_caption_languages |
{ "url": "..." } |
Caption tracks and translation targets |
get_key_moments |
{ "url": "...", "count": 8 } |
LLM-picked moments with verified timestamps |
get_skip_segments |
{ "url": "..." } |
SponsorBlock segments and the transcript share they cover |
find_duplicates |
{ "collection": "g-wagon-1a2b3c4d" } |
Reuploads and clips, so a brief counts each source once |
digest_videos |
{ "collection": "g-wagon-1a2b3c4d", "question": "..." } |
A cited digest made with the client's own model (MCP sampling), or a chunk plan |
choose_caption_track |
{ "url": "..." } |
Asks which caption track to use (MCP elicitation) |
open_transcript_viewer |
{ "url": "..." } |
An interactive transcript viewer in clients with MCP Apps |
watch_live |
{ "url": "...", "maxMinutes": 10 } |
A rolling transcript of a live stream (needs ffmpeg) |
export_obsidian |
{ "collection": "...", "vaultPath": "/home/me/Vault" } |
Notes written into an Obsidian vault on this machine |
export_notion |
{ "collection": "..." } |
Notion pages (needs a Notion token) |
Prompts#
| Prompt | Arguments | What the agent gets |
|---|---|---|
brief |
collection or urls (comma separated), optional question |
Steps to read the sources outline first and write a brief citing every claim, with consensus and disagreement |
buyer_guide |
product, collection or urls |
The same for a buyer guide: years, known issues, prices, mileage, inspection checklist |
digest |
collection or urls, optional question |
Drives digest_videos |
In Claude Code, prompts appear as slash commands: /mcp__vid2context__brief.
Resources#
| URI | Content |
|---|---|
vid2context://video/{id} |
A saved video context as JSON (id URL-encoded, like youtube%3AjNQXAC9IVRw) |
vid2context://video/{id}/transcript |
Its text transcript with timestamps |
vid2context://collection/{id} |
A collection with per-video summaries and token totals |
vid2context://collection/{id}/latest |
The newest items of a collection |
Resources list what is saved, so they fill up as the agent reads videos.