Inspiration

A 38-minute podcast contains maybe five lines that, in the right order, are a story. Finding them is a search problem with no correct answer — only taste — and you cannot judge a candidate without hearing it.

Both existing options fail. Doing it by hand means scrubbing 547 transcript lines while holding a story in your head. Handing it to an AI means it returns one cut, you feel it's wrong, and you have no vocabulary to say why and no cheap way to explore what's next to it. You bounce back to doing it by hand.

So the design principle isn't "let the agent write the script." It's:

The agent's job is to make your judgment cheap.

What it does

Cutroom registers 33 WebMCP tools on document.modelContext. An agent can search a transcript, propose complete cuts assembled from non-contiguous spans anywhere in the recording, trim to word boundaries, cut on breath, clean out hesitations, and play the result out loud — while the person listening keeps every decision that matters.

Three things make it more than a wrapper:

1. The agent can hear. findEnergyMoments reads the RMS envelope of the real audio and returns where the voice lifts above its own baseline. Editors will tell you the most clippable moment in an hour is rarely the smartest sentence — it's the one with the most life in it. A transcript cannot show you that. On the demo episode the strongest moment it returns is "I mean, isn't that amazing that we did that?" at 29:38 — lift 2.85 over her own baseline — and "are you going to have a giggle fit?" comes fourth. Neither is a line any keyword search ranks, because the signal is in the delivery, not the words. It runs in 1.7ms over 21,000 envelope samples.

2. Taste travels both ways. Agent proposals land as pending clips, never silent overwrites. The human thumbs-down a clip or clicks a steer chip ("Tighter", "Colder open"), and those come back through getReelState as humanVote, humanNote and humanAsked. The agent revises to their taste rather than its own. Every tool call appears in a visible ledger.

3. It knows the craft. checkFlow reports what an editor would flag: a hook that opens mid-thought, a clip starting on a pronoun with no antecedent in the cut, a join that cuts in mid-flow, everything drawn from one stretch. snapToBreath moves cut points to real pauses. cleanUpCut removes stammers and dead air from the middle of a clip and closes the audio up behind them. getCutManifest returns every span to a hundredth of a second, plus an ffmpeg command — we decide what to cut; ffmpeg does the cutting. That command is not a plausible-looking string: run against the demo episode it produces a 30.53s file for a manifest claiming 30.5s across 8 spans, which is AAC frame quantisation and nothing else. exportCut hands over EDL, JSON, a readable script, or SRT captions timed against the finished cut rather than the source, so they burn straight onto the exported video.

Why WebMCP fits this use case

The sponsor demos are transactional — storefronts, reservations, returns. There the agent completes a task you'd rather not do by hand.

Cutting a short isn't that shape. There is no correct answer to find, only a judgement to make, and the person has to hear it to make it. So the work splits cleanly along the one axis that matters:

The agent does The human does
read 547 lines in a second decide which five are a story
hear where the voice lifts across 38 minutes decide whether that's the right kind of energy
trim to word boundaries, cut on breath say "that's too long" after hearing it

Neither half works alone. That's why this is a WebMCP app rather than a chatbot with an API: the tools have to live where the person is listening.

How humans and agents collaborate here

The brief asks for people and agents creating together, so the collaboration runs in both directions rather than one:

  • Agent → human. Proposals land as pending clips on the reel, dashed, each with a one-line reason. Nothing is committed. The human plays them and decides.
  • Human → agent. A thumbs-down on a clip, or a steer chip ("Tighter", "Colder open"), is recorded and returned through getReelState as humanVote, humanNote, humanAsked. The agent revises to their taste rather than its own.
  • Both → visible. Every tool call appears in a live ledger. The human always knows what the agent just did, and can undo it.

Why this needs WebMCP

An agent driving this page through the DOM would scroll 547 lines and guess. With tools it calls searchTranscript("the call came") and gets {startSec: 1122.08, endSec: 1129.78} — a precise operation on the time domain of a recording, which is not something you can click.

And the reads are bidirectional. getReelState doesn't return a number; it returns what the human just did. That's the part a conventional MCP server can't replicate: the tools live where the person is.

How we built it

100% static — no backend, no API keys, no database. The intelligence is the visiting agent; we ship the tools. Vanilla JS, GSAP for motion.

Demo material is NASA's Houston We Have a Podcast ep. 327 (public domain), transcribed locally with whisper.cpp using a verbatim prompt — the default run silently strips disfluencies (0 "um"s), the prompted run keeps them (220), which is exactly what the cleanup tools exist to remove.

Challenges

Cuts derived from audio must be checked against words. An early cleanup pass deleted "their office" from a line because that passage was softly spoken and fell under a global loudness floor. A real pause has no word in it — that's now the test. Same bug in filler removal, where padding past an "uh" swallowed "nobody". The envelope tells you where energy is, not where words are.

Stitched playback is harder than it looks. currentTime still reports the old position while a seek is in flight, so the clip-boundary check re-fired instantly and chained through the whole reel — a 25-second cut collapsed to 14.

A transcript without media. An agent handing us someone else's transcript used to leave the previous audio loaded — playing NASA under their words, every timestamp pointing at the wrong sound. There's now an explicit text-only mode that does everything except hear it.

What we learned

Encoding editorial craft turned out to matter more than adding tools. The tools that make the difference — energy, breath, flow, the human's votes — are the ones that carry judgment, not the ones that carry actions.

Honest limits

  • The tools have only ever been called by an agent in Chrome 149+. Every documented requirement for ChatGPT's browser is met and verified — origin isolation, Permissions-Policy: tools=(self), registration on document.modelContext before first paint — and site tools were enabled on GPT-5.6 Terra. It still reported the bridge as unavailable to it. The Chrome path is the one demonstrated, and bin/verify.sh proves it end to end.
  • A judge with no WebMCP browser gets a scripted demo of the real tool calls, and a heuristic "suggest cuts from the text" fallback that is deliberately labelled as reading only the words. It's visibly worse than the agent, which is the point.
  • 547 transcript lines all live in the DOM. Word-level spans are built as rows come into view, which keeps a full re-render at ~17ms, but a three-hour recording would want real virtualisation.
  • Rendered video is a fragmented MP4 (H.264 + AAC, 1080×1920) — that's what MediaRecorder emits. Browsers and most players open it; QuickTime is occasionally fussy, so the tool hands back the one-line stream copy that fixes it. For anything you'd publish from real video source, take getCutManifest and the ffmpeg command it gives you — which has been run and checked.

What's next

Speaker diarization, so "more of her, less of him" becomes a real instruction. In-browser transcription, to close the media-only gap.

Built With

Share this project:

Updates

Submission history