talkthrough-mcp
The talkthrough-mcp server ingests local video/audio recordings and transforms them into structured, queryable, agent-ready data — entirely locally, with no cloud interaction or telemetry.
Core Tools
process_media– Transcribe speech (Whisper), extract scene-change keyframes, OCR on-screen text, and anchor everything to wall-clock (ISO 8601) time. Idempotent: re-processing the same file (by content hash) returns instantly.get_transcript– Retrieve paginated transcripts as segments, plain text, or SRT for a whole recording or a specific time window.get_frames– Fetch JPEG keyframes nearest to a timestamp or across a range (capped at 6/call, near-duplicates filtered).get_moment– Get a bundled evidence package for a specific moment: transcript slice + up to 3 frames + OCR text + wall-clock range. Ideal for bug triage and report generation.search– Case-insensitive substring search across both spoken transcript and OCR-indexed on-screen text, with timestamps and frame references.extract_frame– Re-extract a full-resolution frame at an exact timestamp from the original source, with optional pixel-level cropping.list_jobs– Browse all processed recordings with filenames, durations, wall-clock start times, and segment/frame counts.
Key Features
Wall-clock anchoring: Every event maps to real ISO 8601 time, enabling correlation with external logs (e.g., grep logs ±30s around a spoken remark).
Fully local & private: ffmpeg, Whisper, and RapidOCR all run on your machine.
~99 languages supported via Whisper auto-detection; configurable OCR languages (Latin, Chinese, Cyrillic, Japanese, etc.).
Lazy retrieval: Agents pull exactly the slice they need — large recordings never flood model context.
Audio-only support:
.mp3,.wav,.m4a,.flac,.ogg— transcript/search work; frame tools gracefully report unavailability.Pre-built prompts:
triage-recording,spec-from-workshop,backlog-from-demo,meeting-actions,correlate-with-logsguide agents through common workflows.CLI:
process,gc, andservecommands for direct pre-processing and job store management.
Integrates with GitHub Copilot CLI to allow AI agents to ingest and analyze narrated screen recordings, extracting transcript segments, keyframes, and OCR text for automated workflows.
Integrates with OpenAI Codex CLI to enable AI agents to process and query narrated screen recordings, turning them into structured data such as transcripts, keyframes, and searchable text.
talkthrough-mcp
Quickstart · Tools · Benchmarks · FAQ · Troubleshooting · Changelog · Contributing
Don't write a bug report. Record it.
Give Claude Code or Codex a narrated .mov/.mp4 — or a public video link —
and talkthrough turns it into searchable transcript, exact frames, OCR and
wall-clock timestamps, locally — so your agent writes an evidence-backed
issue draft or investigates the fix.
Also works for meetings, workshops, product demos, and production incidents.

Illustration — an animation of the /talkthrough:bug storyline, not a screen
capture: the recording is indexed locally (transcript · keyframes · OCR ·
wall-clock), the evidence checkpoint is assembled, and the draft is ready to
file with your own tracker tooling (gh, Jira, a GitHub MCP server —
talkthrough itself never leaves your machine and never posts anything).
Real runs, no animation:
▶ Watch the demo with sound (1:18) — an unedited session: a narrated recording goes in, a ready-to-file
bug-report.mdcomes out.Silent recording → issue draft — the whole thing as files you can re-run: a Playwright-recorded, audio-free
.mp4and the unedited agent output. Every number is reproducible — processing that file givesjob_id 8703a66bbe77a7d0(the job id is the sha256 prefix, soshasum -a 256predicts it), 17 keyframes / 4 unique, and 0 transcript segments because there is no audio track.
Quickstart
One command, no system dependencies: ffmpeg falls back to a bundled build,
OCR is pip-only, and whisper models download themselves on first use. The
only prerequisite is uv (brew install uv or
curl -LsSf https://astral.sh/uv/install.sh | sh).
Cold setup has two separate stages: uvx first resolves a compatible Python
and the pinned server environment, then the first process_media downloads
any missing media/model assets. A plugin update can create a new environment
and, without system ffmpeg, fetch the ~80 MB bundled ffmpeg again; shared
Whisper/OCR/diarization caches and warm, network-free jobs remain reusable.
See Troubleshooting.
Claude Code
Two install paths — pick one, not both (the plugin already includes the server; installing both would register it twice):
Server only — the 9 tools + 6 prompts, and nothing else on your system. Choose this for a minimal setup, or when you manage MCP servers yourself across several clients:
claude mcp add -s user talkthrough -- uvx --python ">=3.11,<3.14" "talkthrough-mcp[diarization,url]"Full plugin — the same server, plus native slash commands
(/talkthrough:bug, /talkthrough:triage-recording, …) that handle the
ceremony for you, a ready-made triage subagent, and an agent skill that
teaches Claude the workflow. Choose this for the best out-of-the-box
experience:
/plugin marketplace add korovin-aa97/talkthrough-mcp
/plugin install talkthrough@talkthroughEvery other MCP client
claude_desktop_config.json:
{
"mcpServers": {
"talkthrough": {
"command": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}More: integrations/claude-desktop/
~/.cursor/mcp.json (or project .cursor/mcp.json):
{
"mcpServers": {
"talkthrough": {
"command": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}More: integrations/cursor/
~/.codex/config.toml (or project-scoped .codex/config.toml in trusted projects):
[mcp_servers.talkthrough]
command = "uvx"
args = ["--python", ">=3.11,<3.14", "talkthrough-mcp[diarization,url]"]More: integrations/codex/
~/.gemini/settings.json:
{
"mcpServers": {
"talkthrough": {
"command": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}More: integrations/gemini-cli/
cline_mcp_settings.json (via MCP Servers UI):
{
"mcpServers": {
"talkthrough": {
"command": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}More: integrations/cline/
~/.openclaw/openclaw.json:
{
"mcp": {
"servers": {
"talkthrough": {
"command": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}
}More: integrations/openclaw/
opencode.json (project) or ~/.config/opencode/opencode.json:
{
"mcp": {
"talkthrough": {
"type": "local",
"command": [
"uvx",
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
],
"enabled": true
}
}
}More: integrations/opencode/
~/.config/goose/config.yaml:
extensions:
talkthrough:
enabled: true
type: stdio
cmd: uvx
args: ["--python", ">=3.11,<3.14", "talkthrough-mcp[diarization,url]"]More: integrations/goose/
~/.copilot/mcp-config.json:
{
"mcpServers": {
"talkthrough": {
"command": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}More: integrations/copilot-cli/
~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"talkthrough": {
"command": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}More: integrations/windsurf/
settings.json (Zed):
{
"context_servers": {
"talkthrough": {
"source": "custom",
"command": {
"path": "uvx",
"args": [
"--python",
">=3.11,<3.14",
"talkthrough-mcp[diarization,url]"
]
}
}
}
}More: integrations/zed/
Any other MCP stdio client uses the same server command: uvx --python ">=3.11,<3.14" "talkthrough-mcp[diarization,url]".
Per-engine folders with exactly these snippets plus verification steps live
in integrations/; agents can self-install via
llms-install.md.
Who said what (speaker diarization) — included in the configs above
Multi-person recordings (meetings, interviews, panels) can carry S1/S2/…
speaker labels. Every install button, snippet, and the plugin above already
ship the [diarization] engine, so asking your agent "who said what" just
works — diarization itself still runs only when requested per call
(process_media(path=..., diarize=true, num_speakers=<count if known>)),
and its models download once on first use.
Prefer the minimal server without the diarization engine? Use
uvx --python ">=3.11,<3.14" talkthrough-mcp as the command instead (the MCP registry entry also
resolves to this lean form) — an explicit diarize=true will then answer
with the one-line install fix. Details in
Speakers.
Upgrading from 0.3.x
Regenerated configs and the plugin carry [diarization,url]. A config you
wrote by hand for 0.3.x — uvx --python ">=3.11,<3.14" talkthrough-mcp, or a
pin without the url extra — upgrades the server in place and lists the new
process_url tool,
but only direct https:// media links work until the extra is there:
YouTube and video pages answer with the one-line install fix. Add url to
your command (uvx --python ">=3.11,<3.14" "talkthrough-mcp[diarization,url]"),
restart the client, and check with talkthrough-mcp --version (0.4.1+),
which names the extras the environment has; the server logs the same line
to stderr at every start, so your client's MCP log shows it too.
Local checkout (development)
git clone https://github.com/korovin-aa97/talkthrough-mcp
claude mcp add talkthrough -- uv run --directory /path/to/talkthrough-mcp talkthrough-mcpThen, in your agent:
Process
~/Desktop/recording.movand triage it — or just invoke thetriage-recordingserver prompt.
Related MCP server: pincushion-mcp
Tools
Tool | What it does |
| Ingest a video/audio file: local STT, keyframes, OCR, wall-clock, opt-in speaker labels. Returns a compact summary. Idempotent by content hash — re-calls are instant; |
| The one network tool: download one public video/audio URL once (a direct |
| Paginated transcript as |
| Keyframe images nearest a timestamp or evenly thinned across a range (unique frames by default, max 6/call); each frame names its absolute |
| The "one remark" bundle: transcript slice + up to 3 frames + their OCR text + wall-clock range (+ |
| Substring search over the transcript AND on-screen OCR text. |
| Atomically persist verified names for anonymous speaker labels. Raw |
| Exact-timestamp full-resolution re-extract from the source video (optional crop) when keyframes miss the instant; returns the file's absolute |
| Recent processed recordings with source paths, durations, wall-clock starts, counts, speaker counts when diarized, and the provider/id for URL jobs. |
Every tool description ships 10+ usage examples, so agents pick the right tool without extra prompting.
Server prompts (slash commands in MCP clients)
Prompt | Workflow |
| One recording → evidence-backed GitHub issue draft (silent, narration-free recordings work too) |
| Narrated screencast → precise findings JSON (bug/feature/question routing, frame evidence) |
| Recorded workshop → structured spec with quoted decisions and open questions |
| Product demo → prioritized backlog with timestamped evidence |
| Meeting audio → action items, decisions, open questions |
| Recording remarks ↔ system logs via wall-clock windows |
The same prompts live as plain files in examples/prompts/
if your client doesn't surface MCP prompts. The findings contract used by
triage-recording is examples/output-contract.schema.json.
Works as a skill too (no MCP required)
The same workflow ships as a cross-engine Agent Skill
at .agents/skills/talkthrough/ — Claude Code,
Codex CLI ($talkthrough), Cursor, Copilot, Gemini CLI, Goose and other
SKILL.md-compatible tools read it. Agents without MCP wiring can drive the
CLI directly: talkthrough-mcp process recording.mov --json prints the
same summary the MCP tool returns, and the job store is shared either way.
Wall-clock anchoring
Every timestamped result carries both t_ms (video-relative) and t_wall
(ISO 8601 real time) once the recording start is known. Resolution ladder:
recorded_atparameter (agent/user override) → confidenceexactQuickTime
com.apple.quicktime.creationdatetag, carries the local timezone (QuickTime Player recordings; ⌘⇧5 wrote it before macOS 26) →highContainer
creation_timetag (UTC) →medium— macOS 26+ ⌘⇧5/ReplayKit screen recordings land here (nocreationdatetag anymore); passrecorded_at=when local-tzt_wallmattersFile mtime minus duration (recorders finalize files at recording END) →
lowNothing → tools still work with relative
t_msonly
Why it matters: "the upload spinner froze here" becomes a ±30 s grep window in your server logs.
Speakers (optional diarization)
With the [diarization] extra installed (included in every generated config —
see Quickstart),
process_media(diarize=true) labels who said what — locally, like everything
else here (sherpa-onnx runtime, no
torch, no accounts, no GPU):
Speakers become
S1,S2, … in order of first appearance; new diarized jobs split speaker changes at word boundaries, while old jobs continue to report honest segment-level precision. Every transcript segment gets aspeaker, and the tools surface it everywhere — roster with talk time inget_transcript,speakers_in_rangeinget_moment,speakeronsearchhits,S1:prefixes in the text/SRT formats, a speaker count inlist_jobs.Know the headcount? Pass
num_speakers. Clustering toward an exact k removes the main failure mode of unknown-count mode (similar voices merging or one voice splitting). It is a target, not a guarantee: the clusterer can converge on fewer clusters than k, and a re-run that changed nothing says so in the payload (labels_changed: false). Agents are instructed to pass the headcount via the tool guidance; do the same in your own calls.Already processed a recording? Calling
process_media(diarize=true)on it re-runs only diarization — whisper is not re-run, and labels land in the existing job. Same for changingnum_speakers. The diarization stage itself still re-scans the full audio: minutes on long recordings.Full rebuilds keep named jobs safe. If a job has active or pending names,
force=truealso requiresdiarize=true; otherwise the call refuses before changing stored data. A successful force rebuilds in staging and moves every previous identity to pending review against the fresh roster. Any processing or commit failure leaves the prior manifest and frames intact.Labels start anonymous. After checking self-introductions, vocatives, or video evidence, call
label_speakersto preserve a verified mapping such asS1→ "Alice" across sessions. The roster can expose bounded OCRname_candidates, but those are raw hints and are never saved automatically. The raw label remains present besidespeaker_name. If a later diarization amend changes the labels, verified names stop being active and move to boundedspeaker_names_pending_reviewevidence instead of being silently lost. Re-check the current roster and explicitly confirm or remove each affected label withlabel_speakers.Video jobs produced before 0.3.1 keep their original flat OCR and can return
name_candidates_noteto explain why hints are absent. They remain fully readable without migration;force=true, diarize=trueregenerates line-aware OCR while preserving saved identities for review.
Models download once (~47 MB total) from pinned, checksum-verified URLs into
~/.talkthrough/models/; warm runs are zero-network like the rest of the
pipeline. Speed on an M-series CPU (4 threads): a 26-minute meeting diarizes
in about 2 minutes (RTF ≈ 0.08), on top of the transcription time. Memory:
expect on the order of 1–1.5 GB peak RSS while an hour-plus meeting is being
diarized (measured on a real 73-minute recording); it is released when the
stage completes.
Role | Model | Download | Weights license |
Segmentation | pyannote segmentation-3.0 (ONNX export by k2-fsa) | 7 MB | MIT |
Embedding (default) | NeMo | 40 MB | Apache-2.0 (per its NGC model card, the NeMo Toolkit license) |
Embedding (alt) | WeSpeaker | 27 MB | CC-BY-4.0 |
Embedding (alt) | 3D-Speaker | 30 MB | Apache-2.0 |
The default won a real-meeting accept-eval (RU/EN/ES + a 3-speaker 26-minute
meeting): it was the only candidate to isolate all three real voices at
num_speakers=3, at 2× the speed of the runner-up.
Pick an alternate embedding model (or point at your own .onnx file for
offline machines) via TALKTHROUGH_DIARIZATION_EMB_MODEL; tune the
unknown-count sensitivity via TALKTHROUGH_DIARIZATION_THRESHOLD (see
docs/TROUBLESHOOTING.md). Honest quality notes
live in Limitations.
Privacy
Everything runs locally: your recordings never leave your machine, speech is
transcribed by a local whisper model, OCR and speaker diarization are local
ONNX inference, and there is no telemetry. For local files the only network
access is one-time tool/model downloads (ffmpeg build, whisper model, OCR
models, diarization models — the latter pinned by URL + sha256). The one
deliberate exception is process_url: it downloads the public source you
name from its provider or CDN, once, and nothing else — no media ever goes
up, no cloud STT or LLM is called, and after that download every tool on
the job is network-free again. The raw URL (which may carry signed tokens)
is not stored: the job keeps a hash, the public provider id or host and a
bounded title. Diarization keeps no voiceprint database: voice embeddings
live only in process memory, and only anonymous turn labels (S1/S2) land
on disk. Your agent sees only the payloads the MCP tools return (text and
selected frames) in your existing session; talkthrough itself makes no LLM
calls.
Languages
Narration in any of Whisper's ~99 languages works: the language is
auto-detected per recording, and the summary reports both language and
language_probability so agents can tell a confident detection from a shaky
one (silence or music at the start can fool the detector — pin it with
language="ru" and force=true when that happens). Speaker diarization is
acoustic — it fingerprints voices, not words — so it is language-independent
and works across all of those languages unchanged.
Pick the model for your languages — per call (model= parameter, agents do
this themselves when a transcript comes back garbled) or as the server
default (TALKTHROUGH_WHISPER_MODEL):
Model | Size | Best for |
| 464 MB | English and major-language narration on CPU |
| ~1.5 GB | recommended for non-English — near-large quality at near-small speed |
| ~1.5 GB | conservative alternative to turbo |
| 75–145 MB | quick drafts, CI |
| — | English-only, slightly faster/better for EN |
Tips that work in every language: pass product names via
vocabulary="Term1, Term2" (biases the decoder so jargon survives), and note
that the workflow prompts instruct agents to write digests in the
narrator's language while keeping quotes verbatim — the server never
translates (exact quotes are evidence; translation is the agent's job).
On-screen text (OCR) defaults to RapidOCR's Latin + Chinese models. For other
scripts set TALKTHROUGH_OCR_LANG to your language — ru/uk (→ the
eslav pack), ja, ko, ar, hi, el, th, or any RapidOCR pack name
like cyrillic — and reprocess with force=true; the matching recognition
model downloads once. Spoken-language support is unaffected either way.
Configuration
Env var | Default | Meaning |
|
| default whisper model ( |
|
| set |
| Latin+Chinese | recognition script for on-screen text: a language code ( |
| — | advanced: JSON object of raw RapidOCR params merged over the derived ones, e.g. |
|
| set |
|
| clustering sensitivity when |
|
| segmentation model: allowlist name or a path to a local |
|
| embedding model: allowlist name (see Speakers) or a local |
|
| ONNX threads for both diarization models |
|
| max media duration (also checked against provider metadata before a |
|
| keyframe budget per job, spread across the whole duration (the 1 s selection floor auto-grows to |
|
| hard cap for one |
|
| job store root (URL jobs keep their downloaded source under |
CLI
The pipeline is also a CLI — useful for pre-processing long recordings outside an agent session (the store is content-addressed, so the agent then queries the same job instantly):
talkthrough-mcp process ~/Videos/long-session.mov # prints the summary
talkthrough-mcp process demo.mov --json # machine-readable
talkthrough-mcp process sync.m4a --diarize --num-speakers 3 # who said what
talkthrough-mcp process-url "https://youtu.be/nHfGfEiVdE8" # one public URL, downloaded once
talkthrough-mcp gc --keep-days 30 # clean the job store (sources go with their jobs)
talkthrough-mcp serve # stdio MCP server (default)
talkthrough-mcp --version # package version + which extras this environment has--json keeps stdout machine-readable on failure too (0.4.1), including a
missing argument or unknown option: the process exits with code 2, stderr
carries the human error: … line, and stdout
carries one JSON document, {"error": {"type": "UnsupportedUrlError", "message": "…"}}. --version also says which optional extras the
environment has — the quickest check when a hand-written config launches
the minimal server (a launcher without the url extra) that advertises
process_url but can only read direct media links; the server logs the
same line to stderr at every start.
First run notes: missing system ffmpeg triggers a one-time static-ffmpeg
download; the first transcription downloads the whisper model (~460 MB for
small); both are cached. After that, expect roughly 3× faster than real time
on an Apple-Silicon CPU with the default model, OCR included (a 2-minute clip
processes in ~40 s) — and instant re-runs on the same file. Progress streams
as MCP progress notifications, and the CLI prints stage lines. More:
docs/TROUBLESHOOTING.md.
Windows
CI runs lint, the unit suite, a full CLI smoke, and a diarize smoke on
windows-latest (static-ffmpeg Windows build, whisper tiny transcription,
OCR, the instant idempotent re-run, and a speaker-roster assert through the
native sherpa-onnx stack). Notes: the per-job lock always serializes threads;
POSIX also uses fcntl for cross-process locking. Quote paths with spaces
(uv run talkthrough-mcp process "C:\Videos\Screen Recording.mp4").
If something breaks, please open an issue.
Supported inputs
Video: .mov .mp4 .webm .mkv .ogv — audio-only: .m4a .mp3 .wav .ogg
.flac (transcript tools only; frame tools explain why they're unavailable).
URLs (via process_url, since 0.4.0): a direct https:// link to one of
those media files; one public YouTube video (watch, youtu.be, shorts,
a completed live); or any public video page yt-dlp can read — public
Instagram reels, TikTok, Wikimedia Commons file pages (each verified on
release day), the rest of yt-dlp's ~1800 site extractors and pages with a
plain HTML5/HLS player (the [url] extra, which the generated configs above
already carry, brings yt-dlp). Always anonymous: a site that demands a
sign-in from anonymous clients (Vimeo does, with this yt-dlp) is refused
with the reason. The source is
downloaded once, kept inside the job, and never re-fetched for later
questions. Not supported: playlists, channels, active live streams,
private, members-only, age-restricted or DRM-protected videos, cookies or
logins — a site that hides a video behind a login or a bot wall answers
with a clear refusal, not a workaround (Instagram in particular rate-limits
anonymous access). You are responsible for having the right to download
and process what you point it at; talkthrough does not bypass any
restriction.
Limitations
Honest edges, so you can decide fast:
Speaker labels are word-level and opt-in on new jobs. Each Whisper word is assigned by maximum overlap with the diarization turns, so fast exchanges split without losing the raw label. Old jobs remain readable and honestly report
attribution_precision="segment"; reprocess withforce=trueto add word timings. Sub-second interjections ("yeah", "mhm") can still be absorbed when the diarization engine does not detect a separate turn, and heavy crosstalk degrades clustering (the segmentation model tracks at most 2 simultaneous voices). Quality is pyannote-3.x-generation. The comfort zone without hints is roughly 2–8 speakers; passnum_speakerswhenever the headcount is known — it removes the worst failure mode at any size, and it is the way to go for large meetings (10+).URL ingestion covers one public video at a time, without logins. Direct HTTPS media links, single public YouTube videos and public video pages yt-dlp can read; playlists, channels, live streams, gated or DRM content and anything behind a login or a bot wall are refused with a reason. Sites change; a page that worked yesterday can need a newer yt-dlp tomorrow. A provider's upload date is not a recording time, so URL jobs have
wall_clock: nullunless you passrecorded_at.Memory: budget about 2 GB for a cold run. Whisper, the OCR models and the frame pass live in one process. A 78-second video on
tinywith OCR peaked at 1.6 GB RSS during the 0.4.0 release QA (download included); the defaultsmallmodel needs more, larger models proportionally so. An 8 GB laptop copes; on anything tighter keep the model small or run the CLI ahead of the agent session.Keyframes + transcript, not motion analysis. A glitch between scene changes can be invisible in the frame set;
extract_framere-checks any instant, but frame-by-frame motion reasoning is your multimodal model's job.STT quality tracks the model you pick. The default
smallfavors speed; non-English narration wantsmodel="large-v3-turbo"(see Languages).OCR reads crisp UI text well; tiny or low-contrast print is best-effort.
Wall-clock confidence depends on recorder metadata — worst case pass
recorded_at=(see the ladder above).Windows caveats — POSIX lock degrades to a no-op; see the Windows section above.
How it compares
talkthrough | cloud recorder SaaS | meeting notetakers | typical video-analyzer MCPs | |
Runs fully locally | ✅ | ❌ | ❌ | varies |
Any local video/audio file | ✅ | browser/app captures | meetings only | ✅ |
Public video URL: downloaded once, kept with the job, analyzed locally | ✅ | n/a | n/a | temp download, often cloud analysis |
Wall-clock anchoring (log correlation) | ✅ | ❌ | ❌ | ❌ |
Who-said-what speaker labels | ✅ local, opt-in | some | ✅ cloud | ❌ |
Ships agent workflows (prompts, skill, findings contract) | ✅ | ❌ | ❌ | ❌ |
OCR of on-screen text, searchable | ✅ | some | ❌ | rare |
FAQ
Why not just upload the video to a multimodal model (e.g. Gemini)?
For a short, non-sensitive clip — do that. The trade-offs appear with length
and sensitivity: an hour of screen recording costs on the order of a million
tokens per question, the file leaves your machine, and you still can't map a
remark to 14:32:07 UTC to grep your server logs. talkthrough indexes once,
locally, then answers any number of follow-ups from the index.
Why not screenpipe?
Different job. screenpipe is an always-on recorder of your machine going
forward (commercial license). It can't open the .mov a teammate or customer
just sent you. talkthrough analyzes any file it's handed — the two compose
fine.
There are agent skills that "watch" videos. Why a server with an index? Watch-style skills push a budgeted frame dump into the context window (and go sparse on long videos), often call cloud STT for the audio, and keep nothing. talkthrough builds a persistent local index — transcript + OCR, full-text searchable — retrieves exact frames lazily, anchors everything to wall-clock time, and answers the next question without reprocessing.
I use Jam for bug reports — do I need this? Keep Jam for browser bugs: console+network captured at record time is great evidence. talkthrough covers what a browser extension can't — desktop apps, mobile screencasts, ops incidents, meetings, any file — with no account, and correlates with server-side logs via wall-clock time.
Which agent model do I need to drive this?
For v0.4.0 the six model configs below ran 61 isolated behaviour cells on
URL ingestion (YouTube, a TikTok page, a speechless Instagram reel,
playlist refusal, the missing wall clock) and the 0.3.2 integrity fixes;
every cell passes on every runner after two product fixes the first
attempts exposed (Codex needs a per-tool approval for the open-world
process_url, and the server now says itself when a URL job has no wall
clock). For v0.3.0 we ran 210 isolated agent cells across 6 model configs (Claude
haiku/sonnet/opus, Codex gpt-5.5 at two reasoning efforts, and gpt-5.4-mini)
and 35 logical scenarios on 5 real recordings plus safety and speaker-label
fixtures. All 102 LLM-judged full-grid results and every mechanical zero were
manually audited; the 30 new speaker behavior runs passed, and old-server
control left 0 release-caused regressions. This is a model-drift snapshot,
not a leaderboard: see the current matrix in
docs/MODEL-NOTES.md. The chart and narrative in
benchmarks/ remain the historical v0.2.0 snapshot.
Can't I just script ffmpeg + whisper myself?
Yes — that's exactly this pipeline. What you'd be rebuilding: scene-change
detection with perceptual dedup, OCR, transcript+OCR search, the wall-clock
ladder, MCP tools with embedded usage examples, six workflow prompts, and a
findings contract. One uvx command instead of an afternoon of glue.
Is it really local? What leaves my machine?
Nothing goes up, ever. For local files the network is used only for one-time
downloads (ffmpeg build, whisper/OCR/diarization models). process_url is
the single tool that talks to the network at runtime, and only down: it
fetches the public source you named, once. No telemetry. See
Privacy — and SECURITY.md treats a violation of
this promise as a vulnerability.
For agents & tooling
Machine-readable entry points, so AI agents can install and use this server without a human reading docs:
llms-install.md— step-by-step install instructions for agentsllms.txt— index of the documentation.agents/skills/talkthrough/SKILL.md— an Agent Skill teaching the tool workflow; discovered automatically inside a checkout by Codex CLI ($talkthrough) and readable by Claude Code, Cursor, Copilot, Gemini CLI and other SKILL.md-compatible toolsAGENTS.md— instructions for coding agents contributing to this reposerver.json— MCP registry manifestintegrations/— per-engine adapters, all generated from one source of truth and drift-tested (incl. the Claude Code plugin underintegrations/claude-code/)docs/URL_ACCEPTANCE_CORPUS.md— the live URL corpus behind the release QA ofprocess_url(manual, needs the network; CI stays offline)
Roadmap
cloud STT · embeddings/semantic search · hosted/remote mode · .mcpb
bundle · whisper.cpp backend
License
MIT
Available Tools
9 toolsextract_frameAIdempotent
Re-extract ONE frame at an exact timestamp from the ORIGINAL source video at native resolution, with an optional crop {x, y, w, h} in source pixels. Use when the stored keyframes miss the instant (they capture scene changes + a 1 fps floor) or when you need full-resolution detail. Slower than get_frames — it decodes the source file, which must still exist at its recorded path (URL jobs keep their downloaded source inside the job, no network). When NOT to use: normal browsing — get_frames serves stored keyframes instantly without touching the source. Examples:
keyframes sit at 12:31 and 12:38 but the flash happened at 12:34.5 → extract_frame(job_id, at_ms=754500)
extract_frame(job_id="...", at_ms=754500, crop={"x":800,"y":40,"w":400,"h":120}) — zoom into the toast text
tiny UI text unreadable in the 1568px keyframe → extract_frame at the same ms for native resolution
verify a one-frame glitch: extract_frame at 12300, 12400, 12500 and compare
OCR missed small text → extract_frame with a tight crop, then read the returned image
crop coordinates are SOURCE pixels (a Retina screen recording may be 2940x1912) — not keyframe scale
response JSON carries "path" (absolute) — "save this screenshot next to my docs" = copy from path yourself
source file moved or deleted → clear error; stored keyframes via get_frames still work
audio-only job → always errors: there is no video stream to decode
anti-example: "show me around 5:00" → get_frames(job_id, at_ms=300000); extract_frame is for exact instants
anti-example: scanning a range frame by frame → get_frames(start_ms, end_ms) first, refine once after
| Name | Required | Description | Default |
|---|---|---|---|
| crop | No | ||
| at_ms | Yes | ||
| job_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations only provide idempotentHint, but the description discloses much more: performance cost, source-file dependency, URL-job storage behavior, audio-only failure, error behavior when source is moved or deleted, and the response JSON's 'path' field. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but dense; every block — use cases, not-to-use guidance, examples, anti-examples, caveats — adds operational value. The core purpose is front-loaded, and the detail is justified by the tool's subtle relationship to get_frames.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no output schema, the description covers inputs, failure modes, performance trade-offs, and result handling sufficiently for an agent to select and invoke it correctly. The 'copy from path yourself' note closes a real usage ambiguity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden and succeeds: at_ms is tied to exact timestamps with concrete ms examples, crop is fully defined with {x, y, w, h} and source-pixel semantics, and job_id is shown in call examples.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb ('Re-extract') and precise scope: one frame, exact timestamp, original source video, native resolution, optional crop. The repeated contrast with get_frames makes it unmistakable which sibling tool is meant.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use it (keyframes miss the instant, need full-resolution detail) and when not to use it (normal browsing, range scanning), with anti-examples routing to get_frames. This is strong, unambiguous selection guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_framesARead-onlyIdempotent
Fetch stored keyframe images (JPEG, <=1568px wide) as MCP image content: the frames nearest to at_ms, OR unique frames across [start_ms, end_ms] evenly thinned to max_frames. Serves unique frames by default (near-duplicates from static scenes are filtered); hard cap 6 images per call. When NOT to use: exact instants between keyframes or native-resolution detail (use extract_frame), or finding on-screen text (use search — OCR text is indexed). Examples:
get_frames(job_id="...", at_ms=83500) — what was on screen when the remark at 1:23.5 was spoken
get_frames(job_id="...", at_ms=83500, max_frames=2) — tighter context, fewer tokens
get_frames(job_id="...", start_ms=0, end_ms=600000, max_frames=6) — overview strip of the first 10 min
get_frames(job_id="...", start_ms=290000, end_ms=310000, include_duplicates=true) — every capture near 5:00
transcript hit at t_ms=421500 → get_frames(job_id, at_ms=421500) for the visual evidence
walking a demo scene by scene → one ranged call per scene beats one giant range
frame files are named by video-ms (t00083500.jpg ↔ t_ms 83500) — stable refs for findings
valid_from_ms/valid_to_ms on each frame = when the screen looked like this — check the span covers your moment
keep max_frames at 2-4 unless you are truly comparing scenes; images are token-expensive
every frame entry carries "path" (absolute) — save/copy the image elsewhere with your own file tools
audio-only job → this tool errors by design; use get_transcript / get_moment instead
anti-example: need EXACTLY 12:34.500 between two keyframes → extract_frame(job_id, at_ms=754500)
anti-example: "find the screen with the red error banner" → search(job_id, "error") first, then jump
| Name | Required | Description | Default |
|---|---|---|---|
| at_ms | No | ||
| end_ms | No | ||
| job_id | Yes | ||
| start_ms | No | ||
| max_frames | No | ||
| include_duplicates | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only/idempotent safe, but the description adds substantial behavioral detail: hard cap of 6 images, deduplication of near-duplicates, error on audio-only jobs, file naming convention, valid_from_ms/valid_to_ms semantics, and path field disclosure. No contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but efficiently structured: core behavior, when-not, examples, anti-examples, and field tips. Every bullet adds actionable info; front-loaded with the primary fetch behavior, and the length is justified by the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema, the description compensates by explaining return content (MCP images, path, valid_from/valid_to), error cases, and practical usage limits. Complete for an agent to select and invoke correctly without additional context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has 0% description coverage, so description must and does explain semantics. It clarifies at_ms (nearest), start/end_ms (range), max_frames (thinning), include_duplicates (every capture), and job_id via examples, fully compensating for the schema gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb ('Fetch') and resource ('stored keyframe images') plus format constraints (JPEG, <=1568px). It clearly differentiates from siblings by naming extract_frame for exact instants/native resolution and search for text, and get_transcript/get_moment for audio-only jobs.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly lists 'When NOT to use' with alternatives (extract_frame, search) and provides anti-examples. Examples show when to use, including ranged vs at_ms, include_duplicates, and transcript-driven lookups, giving clear decision guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_momentARead-onlyIdempotent
The "one remark" evidence bundle: transcript slice + up to 3 unique frames + their OCR text + the wall-clock range for [start_ms, end_ms], in a single call. This is the workhorse for triage: one call per finding gives you the quote, the screenshot, and the on-screen text. When NOT to use: broad exploration (get_transcript / get_frames) or keyword lookup (search). Examples:
get_moment(job_id="...", start_ms=83000, end_ms=97000) — full evidence for the remark at 1:23-1:37
segment seq 12 spans t0_ms=83210, t1_ms=96800 → get_moment(job_id, 83210, 96800)
pad ±2000 ms around the spoken range — narrators react to things already on screen
triage loop: for each candidate finding, exactly one get_moment call → quote + frame + OCR
user: "what was I showing when I said 'this button is broken'?" → search first, then get_moment at the hit
opening context of a meeting: get_moment(job_id, 0, 15000)
response includes the t_wall range when known → quote it in bug reports for log correlation
diarized job → speakers_in_range + speaker on each segment: who is talking in this window, at a glance
frame entries carry "path" (absolute) — copy the screenshot elsewhere with your own file tools
"was X on screen at t?" → yes iff some frame's valid_from_ms <= t < valid_to_ms — no extra calls needed
audio-only job → returns the transcript slice plus a no-frames note (that is expected)
anti-example: whole-video summary → get_transcript(format="text"), not a chain of get_moments
anti-example: need more than 3 frames of a range → get_frames(start_ms=..., end_ms=..., max_frames=6)
keep ranges under ~30 s; a 5-min "moment" dilutes the bundle and wastes tokens
| Name | Required | Description | Default |
|---|---|---|---|
| end_ms | Yes | ||
| job_id | Yes | ||
| start_ms | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds behavioral details beyond annotations: returns up to 3 frames, OCR, wall-clock range, audio-only handling, frame paths. No contradictions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Very informative but lengthy. However, it is well-structured with clear sections and front-loaded key purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Complete for a tool with no output schema: covers return structure, edge cases (audio-only, diarized), and usage patterns.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema coverage, the description compensates fully, explaining start_ms/end_ms with examples, padding advice, and use cases.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Explicitly states it returns an evidence bundle with transcript slice, up to 3 frames, OCR, and time range. Distinguishes from siblings like get_transcript and search.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when to use (one call per finding) and when not to use (broad exploration, keyword lookup). Includes multiple examples and anti-examples.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_transcriptARead-onlyIdempotent
Retrieve the transcript of a processed job, lazily and paginated. Formats: "segments" (default — seq, t_ms, t_wall when known, speaker when diarized, text), "text" (plain prose; "S1:" prefixes at speaker changes), "srt" (subtitles, speaker-prefixed cues). Diarized jobs also return the roster, attribution_precision, saved speaker_name values, raw OCR name_candidates, and bounded pending-review names plus old-roster context after a relabel. Pre-0.3.1 video jobs may return name_candidates_note because their flat OCR is readable but less useful for hints. Pending names are evidence to re-check, never active identities. A stale pending label can only be removed with label_speakers(..., labels={"Sx":null}). Raw S labels remain canonical. Responses are capped (~8k tokens): when truncated=true, continue from the returned next_start_ms. When NOT to use: to find one keyword (use search) or to inspect one moment with visuals (use get_moment). Examples:
get_transcript(job_id="a1b2c3d4e5f60718") — whole transcript of a short recording
get_transcript(job_id="a1b2c3d4e5f60718", start_ms=0, end_ms=120000) — just the first two minutes
get_transcript(job_id="...", format="text") — prose block for summarization
get_transcript(job_id="...", format="srt") — subtitle export the user asked for
diarized job: segments carry "speaker" + a roster header (top-12 by talk time; speakers_truncated counts the rest)
"what did S2 say?" → format="segments", collect entries with speaker=="S2" (labels are in order of first voice)
got truncated=true with next_start_ms=421500 → get_transcript(job_id="...", start_ms=421500)
user: "what was said between 5:00 and 6:30?" → start_ms=300000, end_ms=390000
legacy video name_candidates_note → explain the limitation; safe regeneration uses force=true+diarize=true
correlate speech with logs: each segment's t_wall lines up with your log timestamps
no speaker fields on a meeting job → re-run process_media with diarize=true (adds them without re-transcribing)
attribution_precision="segment" → force=true+diarize=true is required for exact word boundaries
pending context points to old evidence; stale labels accept null removal, never a new name
anti-example: "where did they mention checkout?" → search(job_id, "checkout"), not full paging
anti-example: screenshots around a remark → get_moment(job_id, start_ms, end_ms)
| Name | Required | Description | Default |
|---|---|---|---|
| end_ms | No | ||
| format | No | segments | |
| job_id | Yes | ||
| start_ms | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark readOnly, idempotent, non-destructive. The description substantially adds context: pagination truncation with next_start_ms, diarized roster details, speaker label semantics, stale-label removal, and legacy caveats. No contradiction with annotations; description enriches the safety profile with operational behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Though lengthy, it is dense and well-organized: core purpose first, then format spec, then pagination, then 'When NOT to use', then a long list of concrete examples with anti-examples. Every sentence adds value—no filler. Structure front-loads the most critical info and uses formatting (bullets) to aid scanning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has complex behavior (diarization, multiple formats, pagination, legacy notes). The description covers every aspect an agent needs to call it correctly: output shapes, pagination continuation, roster/attribution details, speaker label handling, and correlation with logs. No missing context even without an output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must fully compensate. It explains job_id implicitly, start_ms/end_ms for range, and format by listing the three enum values and their output structures. It goes beyond schema by describing what each format returns (segments fields, text prefixes, srt cues) and how to use start_ms for continuation. Complete semantic coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Retrieve'), resource ('transcript of a processed job'), and key behaviors (lazily, paginated). It explicitly differentiates from siblings by naming search and get_moment as alternatives for different intents, so an agent can select it correctly just from the description.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Includes a dedicated 'When NOT to use' section naming alternatives (search, get_moment), plus extensive examples and anti-examples covering format selection, paging, diarized handling, and legacy cases. Guidance is explicit and conditions for choosing alternatives are crystal clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
label_speakersAIdempotent
Persist VERIFIED human-readable names for anonymous S1/S2/… labels on one diarized job. labels is a patch: a 1-100 character name saves it; null or blank removes it. Optional evidence (max 500 characters per label) records why the mapping is trusted. Raw labels remain canonical in JSON; names appear separately and in text/SRT display. The write is atomic, locked, local, and idempotent. OCR name_candidates are raw hints only and are never saved automatically. A relabelled job can carry pending-review names with source-roster anchors. For a label still in the roster, an explicit patch confirms, replaces, or removes only that entry. For a stale pending label, only an explicit null removes its name, evidence, and context.
When NOT to use: before diarization, or when a name is only a guess without human/screen evidence.
Examples:
label_speakers(job_id="...", labels={"S1":"Vera"}) — save one verified mapping
label_speakers(job_id="...", labels={"S1":"Vera","S2":"Tom"}) — patch several labels
save evidence: label_speakers(job_id="...", labels={"S1":"Vera"}, evidence={"S1":"intro at 1200ms"})
screen name plate confirms S2 → save the name and cite its frame timestamp in evidence
label_speakers(job_id="...", labels={"S2":null}) — remove S2's name and evidence together
label_speakers(job_id="...", labels={"S2":" "}) — blank also removes the mapping
named S1, new proof → label_speakers(job_id="...", labels={}, evidence={"S1":"title card at 0ms"})
two Alexanders may map to S2 and S5 — duplicate names are allowed and search covers both
response roster carries speaker_name beside label; raw S labels are never replaced
name_candidates may be UI text or another person's name → inspect frames before deciding
unknown label or a name over 100 characters → error lists the valid roster labels
fresh session: get_transcript returns saved names; do not infer the mapping again
stale pending S3 → labels={"S3":null}; never assign a name while S3 is outside the roster
anti-example: uncertain identity → keep S anonymous until evidence verifies the name
| Name | Required | Description | Default |
|---|---|---|---|
| job_id | Yes | ||
| labels | Yes | ||
| evidence | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses atomicity, locking, locality, and idempotency, plus null/blank removal semantics, duplicate-name allowance, and that name_candidates are never persisted automatically. This goes well beyond the annotations' idempotentHint and adds meaningful operational context without contradicting the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core contract and includes a dedicated 'When NOT to use' section, but the long example list contains some redundancy (e.g., null and blank removal are both demonstrated). Still, the length is largely warranted by the tool's edge-case-heavy behavior.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers success behavior, error conditions, the response roster carrying speaker_name, interaction with stale labels, and the relationship to get_transcript and name_candidates. With the output schema present, nothing an agent needs to call this safely is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, but the description fully compensates: labels is defined as a patch where 1-100 character names save and null/blank removes, evidence is capped at 500 characters per label, and job_id refers to a diarized job. Concrete examples map each parameter to realistic calls.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence names a precise verb and resource: 'Persist VERIFIED human-readable names for anonymous S1/S2/... labels on one diarized job.' It also distinguishes itself from get_transcript by clarifying names are a separate layer from raw labels, and the 'When NOT to use' section reinforces scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when not to use the tool: before diarization or when the name is only a guess without human/screen evidence. It also points to get_transcript for reading already-saved names and warns against assigning names while a label is outside the roster.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_jobsARead-onlyIdempotent
List processed recordings, newest first: job_id, source filename, duration, created, wall-clock start, segment/frame counts. The store is content-addressed — the same file maps to the same job even after renames or moves, and jobs persist across sessions and machines restarts. When NOT to use: as a health check or before every call — job_ids are stable, remember them. Examples:
user: "triage the recording I processed this morning" → list_jobs() → pick by filename + created
user names neither job_id nor path → list_jobs() first; only ask if still ambiguous
resume yesterday's analysis in a fresh conversation → list_jobs() → reuse its job_id directly
file was renamed after processing → match by duration/created; the content hash ignores names
wall_clock.start answers "WHEN was this session?" — pick the job from "yesterday around 15:00"
after CLI batch pre-processing (
talkthrough-mcp process big.mov) the job shows up here — query ittwo jobs with the same filename → the newer created one is usually the re-recording
diarized jobs show "speakers": N — "the 4-person meeting from Tuesday" is findable at a glance
URL jobs carry "origin" (provider, provider_id, title) — "the YouTube video from yesterday" is findable
empty list → nothing processed on this machine yet; ask the user for a file path or a public URL
job disappeared → likely
talkthrough-mcp gccleaned it; re-run process_media on the file (same id)anti-example: checking whether a NEW file is processed → just call process_media, it is idempotent+instant
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false, and the description adds behavioral context: content-addressed store (same file maps to same job despite renames), persistence across restarts, empty list semantics, and gc cleanup behavior. No contradictions with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and output fields, then structured into 'When NOT to use' and examples. It is long but every segment adds decision value; a slight reduction in redundancy would make it tighter, yet nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers ambiguity resolution (same filename, renamed files), temporal queries, CLI interplay, URL jobs, diarization, empty results, and job disappearance. With an output schema present and no input params, this is more than enough contextual guidance for reliable invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters and the input schema coverage is trivially complete at 100%. Per the rubric, a no-parameter tool earns a baseline 4; the description also usefully explains output field semantics (wall_clock.start, origin, speakers) that help an agent interpret rows.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'List processed recordings, newest first' and enumerates the exact fields returned (job_id, source filename, duration, created, wall-clock start, segment/frame counts). This is a specific verb+resource that clearly distinguishes list_jobs from siblings like process_media or get_transcript.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Contains a dedicated 'When NOT to use' section plus 12 examples covering triage, resume, renamed files, CLI pre-processing, duplicate filenames, and missing jobs. The anti-example explicitly routes new-file checks to process_media, leaving no ambiguity about when to call this tool vs alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
process_mediaAIdempotent
Ingest a LOCAL video or audio file and make it queryable: validates the file, transcribes speech locally (whisper), extracts scene-change keyframes, OCRs on-screen text, resolves the wall-clock start time, and (opt-in) labels who said what via local speaker diarization. Returns a compact summary (job_id, media info, wall_clock, transcript preview, speaker roster when diarized) — full data stays on disk and is served lazily by the other tools. Idempotent by content hash: re-calling on an already-processed file returns instantly. For MULTI-PERSON recordings (meetings, interviews, calls) diarize=true is part of a proper analysis — pass it even when the user only asks for a summary. num_speakers is a target the clusterer may not reach, not a constraint — the payload says when a re-run changed nothing (labels_changed). If an amend changes the labels, verified names become pending-review evidence rather than active identities, with old-roster anchors for re-checking. Current pending labels can be confirmed/replaced/removed; stale labels can only be removed with an explicit null patch. Full force reprocessing of a job with saved or pending identities requires diarize=true and preserves every old identity as pending review against the rebuilt roster; without diarization it refuses before changing the stored job. When NOT to use: to re-fetch data you already processed (use the retrieval tools), or for URLs — local file paths only; a public video/audio URL goes to process_url. Examples:
process_media(path="/Users/sam/Desktop/bug-repro.mov") — narrated screencast, defaults are right
meetings: model="large-v3-turbo" + vocabulary=<attendees, terms> + num_speakers=N — turbo's extra cost is trivial
process_media(path="/tmp/standup.m4a") — audio-only: transcript tools work, frame tools will error
process_media(path="/rec/panel.mov", diarize=true, num_speakers=4) — headcount known? ALWAYS pass it: best accuracy
relabel amend → names become pending with old anchors; stale labels are removable only with null
error mentions [diarization] → run uvx --python ">=3.11,<3.14" "talkthrough-mcp[diarization]"
know the attendees? process_media(path=..., vocabulary="Anastasia, Evgenii, OKR") — names+jargon survive STT
user: "analyze/summarize this meeting" → include diarize=true — speaker structure is not optional extra credit
noisy threshold roster (clusters ≫ people)? ASK your user for the real headcount, then re-run with num_speakers=N
cap_hit or sampling_interval_s in summary → for slide hunts raise TALKTHROUGH_MAX_FRAMES or use extract_frame
summary shows wall_clock=null → ask when recording started, re-call with recorded_at=... and force=true
transcript garbled or language_probability low → re-call with model="large-v3-turbo" (or language="ru") + force=true
after success, do NOT dump everything — continue with get_transcript / get_moment / search on the job_id
anti-example: frames from an already-processed job → get_frames(job_id=...), never process_media again
named job + force=true → include diarize=true; old identities return as pending review, never silently vanish
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| force | No | ||
| model | No | ||
| diarize | No | ||
| language | No | ||
| vocabulary | No | ||
| recorded_at | No | ||
| num_speakers | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes far beyond the annotations: it discloses idempotency by content hash, lazy serving from disk, refusal to re-process saved identities without diarize=true, pending-review label behavior, null-patch removal for stale labels, and failure signals like wall_clock=null or cap_hit. These behavioral traits are not visible in the annotations and are critical for correct invocation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with a dense summary, then organized into not-to-use guidance, examples, and troubleshooting. It is long, but the length is mostly earned given 0% schema coverage and the tool's complex diarization/identity edge cases; a slightly tighter arrangement would improve conciseness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a complex ingestion tool with 8 parameters, no schema descriptions, and nuanced behavioral rules, the description is remarkably complete. It covers inputs, outputs, failure modes, parameter semantics, follow-up actions, environment setup for diarization, and explicit when-not-to-use cases. An agent has everything needed to call this tool correctly in a wide range of scenarios.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden, and it delivers. It adds real meaning to path (local only), diarize (required for multi-person), num_speakers (a target, not a constraint), model and language (when to upgrade/re-run), vocabulary (names+jargon survive STT), recorded_at (needed when wall_clock=null), and force (required alongside diarize for named jobs). Every parameter is given operational context beyond its bare schema type.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: ingest a LOCAL video or audio file and make it queryable, then enumerates the concrete pipeline (validate, transcribe, keyframes, OCR, wall-clock, diarization). It clearly differentiates itself from siblings like process_url and the retrieval tools, so an agent can select it without ambiguity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states when NOT to use the tool: re-fetching processed data should use retrieval tools, and URLs belong to process_url. Numerous examples map user intents (meeting summaries, audio-only files, known headcounts, re-processing) to the correct parameters, including an anti-example that warns against re-processing already-processed jobs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
process_urlA
Download ONE public video/audio URL once (this is the only tool that uses the network), then run the same LOCAL pipeline as process_media: transcript, keyframes, OCR, wall-clock, optional diarization. Supported: direct https:// links to a media file (mp4/mov/webm/mkv/ogv/m4a/mp3/wav/ogg/flac), one public YouTube video (watch, youtu.be, shorts, a completed live), and any public video PAGE yt-dlp can read — Instagram (public reels/posts), TikTok, Wikimedia Commons, pages with an HTML5/HLS player, other sites as far as their yt-dlp extractor works anonymously (Vimeo does not). Not supported: playlists, channels, active live streams, private/members-only/age-restricted/DRM videos, cookies/logins; sites that hide a video behind a login or a bot wall fail with a clear reason. The downloaded source is kept inside the job, so extract_frame works later without network; the raw URL is never stored (only a hash, the provider id/host and a bounded title). A repeat call on the same URL serves the stored job without touching the network unless refresh=true. Job ids stay content hashes: the same video from two URLs is one job. YouTube and other pages need the optional [url] extra. The provider's upload date is NOT the recording start: wall_clock stays null unless recorded_at is passed. When NOT to use: for local files (process_media), or to re-fetch data you already processed (use the retrieval tools). Examples:
process_url(url="https://youtu.be/nHfGfEiVdE8") — one public YouTube video, defaults are right
process_url(url="https://www.youtube.com/watch?v=ID&list=PL...") — the playlist part is ignored: ONE video
process_url(url="https://cdn.example.com/recordings/standup.mp4") — direct https link to a media file
process_url(url="https://www.tiktok.com/@nasa/video/7…") — a public video page; origin.provider names the site
meeting from a link: process_url(url=..., diarize=true, num_speakers=3, vocabulary="Vera, Tom, OKR")
non-English narration: process_url(url=..., model="large-v3-turbo", language="ru")
known recording start: process_url(url=..., recorded_at="2026-09-05T14:00:00+02:00") — enables t_wall
the video changed on the provider → process_url(url=..., refresh=true): new download, maybe a new job_id
re-anchor or change the model on a stored URL job → process_url(url=..., recorded_at=..., force=true), no download
error mentions [url] → run uvx --python ">=3.11,<3.14" "talkthrough-mcp[diarization,url]" and restart
playlist / channel / live / private URL → clear error; pass a single public video URL instead
"bot check"/"sign-in" refusal on Instagram/TikTok → the site blocked anonymous access; report it, no workaround
origin.published_at is the provider's upload time, not when the recording was made — never use it as t_wall
after success continue with get_transcript / search / get_moment on the job_id — never re-download
anti-example: a file on disk → process_media(path=...); process_url is only for https URLs
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ||
| force | No | ||
| model | No | ||
| diarize | No | ||
| refresh | No | ||
| language | No | ||
| vocabulary | No | ||
| recorded_at | No | ||
| num_speakers | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover read-only/destructive hints, but the description adds substantial non-obvious behavior: it is the only network-using tool, downloaded sources are retained for later extract_frame, raw URLs are never stored, job ids are content hashes, repeated calls are cached unless refresh=true, and wall_clock stays null unless recorded_at is provided. No contradiction with annotations exists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well structured into core definition, supported/unsupported inputs, behavioral caveats, when-not-to-use, and topical examples. Each section provides distinct operational information; the minor repetition about upload-date versus recording-start is acceptable given the risk of misuse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a network-capable tool with 9 parameters, no parameter descriptions, and an output schema present, the description is exceptionally complete. It covers invocation semantics, failure modes, caching/deduplication, privacy behavior, provider limitations, and follow-up workflow with retrieval tools. Nothing essential for correct use appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry the semantic burden. It does so via examples covering all nine parameters: url, refresh, force, model, language, diarize, num_speakers, vocabulary, and recorded_at. It also explains important parameter interactions, such as force avoiding re-download and playlist parts being ignored in url.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: download exactly one public video/audio URL and run the same local pipeline as process_media. It clearly distinguishes itself from local-file processing and retrieval tools, and enumerates supported and unsupported URL types, making the tool's scope unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description includes an explicit 'When NOT to use' section naming process_media and the retrieval tools, plus an anti-example for local files. The extensive examples also explain when to use refresh, force, diarize, model, language, vocabulary, and recorded_at, giving an agent concrete decision guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchARead-onlyIdempotent
Case-insensitive word search across BOTH transcript segments and frame OCR text. The default match_mode="all_words" requires EVERY query word as a substring; match_mode="any_word" requires at least one (ё and е are interchangeable). Hits carry source (transcript|ocr), t_ms, t_wall when known, the matched text, and the nearest frame position — everything needed to jump straight to evidence. Optional speaker accepts a raw label ("S2") or saved name and narrows to that voice's transcript hits. Duplicate saved names search all matching labels honestly. No embeddings. When NOT to use: fuzzy/semantic questions ("anything about performance?") — page get_transcript and read; regex is not supported. Examples:
search(job_id="...", query="login") — every spoken or on-screen mention of login
user: "what did I say about the login button?" → search(job_id, "login button") → get_moment at hits
search(job_id, "TypeError") — on-screen stack traces and error text are OCR-indexed; great for bug repros
search(job_id, "€49") — prices, IDs, and literals on screen are findable via OCR
take hit.t_wall and grep your server logs ±30 s around it to pair remark ↔ log line
broad lexical recall: search(job_id, "timeout latency", match_mode="any_word")
multi-word = ALL words as substrings, any order: "first phase" hits "the first real phase"
stems beat inflected phrases: "кнопк отправк" finds «Кнопка отправки» and «кнопку отправки»
every hit has nearest_frame_ms → get_frames(job_id, at_ms=) shows the moment
diarized job: transcript hits carry "speaker" — "who mentioned the deadline?" is answered by the hit itself
search(job_id, "deadline", speaker="S2") — only S2's mentions; OCR hits are excluded (screens have no voice)
search(job_id, "deadline", speaker="Vera") — saved names are matched case-insensitively
audio-only job → transcript hits only (there is no OCR index)
anti-example: "summarize the pricing discussion" → get_transcript(format="text") and read it
anti-example: "everything S2 said" → get_transcript and collect speaker=="S2" — search always needs a query
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| job_id | Yes | ||
| speaker | No | ||
| match_mode | No | all_words |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description reveals far more than annotations: case-insensitivity, ё/е equivalence, all_words vs any_word semantics, OCR exclusion when speaker is set, audio-only jobs producing transcript-only hits, duplicate saved name behavior, and the exact fields each hit carries. These behavioral details materially change how an agent invokes and interprets the tool and align with the read-only annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but exceptionally dense and well-structured, opening with core semantics and then organized into examples and anti-examples. Each bullet earns its place by conveying a distinct behavioral or routing fact, and the when-not-to-use section is placed early for quick decision-making.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a search tool with four parameters, an output schema, and several subtle behaviors, the description covers invocation, match semantics, speaker filtering, OCR/transcript scope, return-value contents, and routing to alternatives. The output schema exists and the description complements rather than repeats it, giving an agent everything needed to call and interpret the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description fully carries parameter meaning. It explains query as substring-based with multi-word all-substring matching, match_mode with its default and contrast, speaker as raw label or saved name with case-insensitive matching and OCR exclusion, and job_id is used throughout examples.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Case-insensitive word search across BOTH transcript segments and frame OCR text.' It clearly distinguishes the tool from siblings like get_transcript, get_frames, and get_moment by defining its unique search-over-both-indexes role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says when NOT to use it ('fuzzy/semantic questions' and regex) and directs the agent to get_transcript instead. It also provides positive examples and anti-examples, such as using get_transcript to summarize or collect all of one speaker's lines, making alternatives and exclusions unmistakable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.4.0- Added
process_url
2 tool updates
v0.3.0- Added
label_speakers - Changed
search1 field changed- added
Input schema / properties / match_modeAdded value: +{ + "default": "all_words", + "enum": [ + "all_words", + "any_word" + ], + "title": "Match Mode", + "type": "string" +}
6 tool updates
v0.2.4- Added
extract_frame - Added
get_frames - Added
get_transcript - Added
list_jobs - Added
process_media - Added
search
3 tool updates
v0.2.3- Removed
extract_frame - Removed
list_jobs - Removed
search
3 tool updates
v0.2.0- Removed
get_frames - Removed
get_transcript - Removed
process_media
7 tool updates
v0.1.0- First observed
extract_frame - First observed
get_frames - First observed
get_moment - First observed
get_transcript - First observed
list_jobs - First observed
process_media - First observed
search
TDQS
Scored across 9 tools
Each tool targets a distinct resource/action: local vs URL ingestion, transcript retrieval vs keyword search, stored keyframes vs exact re-extraction vs combined evidence bundles. The descriptions include explicit anti-examples that reinforce the boundaries, making misselection unlikely.
Eight of nine tools follow a clear verb_noun pattern: process_media, process_url, get_transcript, get_frames, get_moment, list_jobs, label_speakers, extract_frame. The single bare verb 'search' is a minor deviation, but the overall naming remains predictable and readable.
Nine tools is well-scoped for a media-analysis server. Each tool maps to a distinct phase of the workflow — ingestion, listing, retrieval, searching, evidence gathering, and speaker identity management — with no redundant additions.
The ingest/read/update side is well covered: process_media and process_url create jobs, list_jobs/get_transcript/get_frames/get_moment/search read them, and label_speakers plus force reprocessing update them. There is no MCP-level job deletion tool (gc is external/CLI-only) and no standalone OCR-only dump, which are minor workaround-level gaps rather than core analysis failures.
Maintenance
Related MCP Connectors
Comment on AI-generated webpages; feedback flows back to your coding agent. Free, MIT, local-first.
Capture feature requests and bug reports from chat into a searchable, AI-categorized backlog.
Voice-powered bug reporting with 13 MCP tools. Record bugs by talking; let AI find and fix them.
Browser-based QA for AI-built software. Test pages with real browsers via agents.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI coding agents to capture screen and voice recordings, extract timestamped frames, and receive structured Markdown reports with context for bug fixing and UI feedback.17 npm18MIT
- AlicenseAqualityDmaintenanceVisual feedback as agent work packets: stakeholders pin on your live app and your AI coding agent reads each pin (selector, screenshot, DOM, thread, acceptance criteria) via MCP and ships the fix.39483 npm1MIT
- AlicenseNot gradedqualityAmaintenanceGives AI coding agents eyes into running applications by recording browser activity and providing session investigation tools for debugging.9 npm3MIT
- AlicenseAqualityAmaintenanceInteractive feedback layer that lets users pin comments on live web apps with auto-captured context (failing requests, console, AI metadata), and coding agents fix issues via MCP, turning pins green upon verification.1052 npmMIT