MCP tools
Connect an agent, and the calls it uses to read and change your timeline without burning tokens.
Register the MCP server once at user scope as fablecut: claude mcp add -s user fablecut -- npx -y fablecut mcp (from npm, no path needed), or from a clone claude mcp add -s user fablecut -- node "<path-to>/fablecut/mcp-server.js". Every Claude Code session then has these tools:
fablecut_status- auto-starts the editor server, returns URL + project summary. Call first.fablecut_docs- returns this document (section: "…"returns only matching##sections).fablecut_get_project/fablecut_set_project- read / replace the timeline JSON.fablecut_get_project {compact:true}returns a one-line-per-clip summary instead.fablecut_patch_project- apply targeted ops (add/update/remove clip/media, set project fields, add and open sequences) without round-tripping the document. Prefer this for edits.fablecut_import_media- copy a local file (or download anhttps://URL) into./media/and register it. The storedsrcis always/media/….fablecut_analyze_reference- turn a reference video into an edit blueprint (shots, beats, BPM, energy, drop) + extract its music. See Remake a reference video.fablecut_frame- look at a moment and get back an actual image.{path, t}returns one frame of a source clip;{path, frames}returns a contact sheet across it; with nopath,{time}renders the composed timeline. See Look at the footage below.fablecut_encode_profiles- list the delivery presets (YouTube, Shorts, Reels, TikTok, X, LinkedIn, Master, Broadcast, Web, Archive, saved ones) and the raw ffmpeg encoding profiles fromencoding-profiles.json.{preset:"youtube"}returns one preset's spec. See Export below.fablecut_normalize_audio- measure clips (BS.1770 LUFS or sample peak, via ffmpeg) and set their clip gain to a loudness target. See Audio mix below.fablecut_auto_duck- find where the voice tracks speak and writeduckkeyframes that dip the music under it. See Audio mix below.fablecut_frame- see a moment.{path, t}→ one JPEG frame of a source clip;{path, frames:12}→ a contact sheet across it;{time}→ the composed timeline, graded, as export renders it. Returns a real image, not numbers. See Look at the footage below.fablecut_denoise- reduce background noise on audio clips (renders a cleaned copy of the file and switches the clip's stems to it). See Audio mix below.fablecut_silence- find the pauses in speech and, withcut:true, cut them out (extract on the targeted tracks, stems in step). See Audio mix below.fablecut_beats- find a music clip's tempo, beat and drop; mark the beats, snap the cuts to them, or re-cut a track's clips on the beat. See Audio mix below.fablecut_reframe- reframe for another shape (9:16, 1:1, 4:5): a copy of the sequence on that canvas whose shots follow the subject. See Sequences below.fablecut_highlights- the best moments of long footage cut into a short edit, in a new sequence. See Sequences below.fablecut_transcribe- turn speech into words with times (Whisper, in the browser), read them on the timeline, find a phrase and cut it out. See Transcripts and text-based editing below.fablecut_captions- caption clips from the transcript, timed word by word (subtitle, karaoke, highlight, word pop); export or import SRT / WebVTT. See Captions below.fablecut_export- render the timeline to a file inexports/with the editor's own Fast export (open tab, or headless Chrome / Edge). See Export below.fablecut_scopes- measure the graded picture at a moment (levels, clipping, colour cast), as the editor's scopes see it; also what a grade layer or a clip's masks select. See Color and Masks below.fablecut_track- motion tracking: follow a region of a video clip, store it as a track, and make a mask or another clip follow it. See Tracking below.fablecut_proxy- make, check or delete proxies: light copies of heavy footage the editor plays while editing (export always reads the originals). See Media management below.fablecut_projects- list, open, create (from a canvas or a saved template), duplicate, rename, annotate, archive and delete projects../project.jsonis always the open one. See Projects below.fablecut_versions- a project's saved versions: save one before big edits, compare a version with now, restore one (never destructive), mark the final cut. See Versions below.
Token-efficient editing important for agents#
Editing via full get→modify→set costs thousands of tokens per change. Cheaper:
- Plan from
fablecut_get_project {compact:true}(≈10× smaller than the JSON) andfablecut_status- fetch the full JSON only to inspect exact keyframes. - Edit with
fablecut_patch_projectops - send only what changes, e.g.{ops:[{op:"updateClip", id:"c_v2", set:{props:{filterPreset:"noir"}}}]}. It re-reads the latest document internally, so it is merge-safe by design (no CONFLICT dance) and never destroys concurrent UI tweaks. To also be sure the clips you touch haven't changed since you looked, passbaseRevision(therevisionyou planned from -fablecut_get_project,fablecut_statusor the last "Patched (revision N)" result): if the project has moved past it, the patch is refused with "CONFLICT - not saved" and the current revision. Re-check the clips, then retry with that revision. - Docs: request
fablecut_docs {section:"schema"}(or Recipes, "Remake", …) instead of the whole manual; skip it entirely if the schema is already in context. - Media questions (duration, resolution, fps, codec, audio channels, file size, how often it is used): read them from the registered media entries or the compact view's MEDIA lines - don't shell out to ffprobe; the editor probes every file and writes them back (see Media management). For what the footage looks like, use
fablecut_frame- it is far cheaper than reading a file, and far more reliable than inferring from a number. - Batch related changes into ONE patch call (ops apply in order, one revision bump).
- Cut and trim with the edit ops (next section) rather than recomputing
start/in/durationby hand - they keep linked stems in sync.
Timeline edit ops fablecut_patch_project#
The editor's own split / ripple / trim code (edit-ops.js), run on the document - so linked audio stems ride along, edits land on the targeted tracks (untargetedTracks, see Semantics), and locked clips stay put, exactly as with the keyboard shortcuts. Times are seconds.
| op | does |
|---|---|
{op:"split", at, ids?} | cut at at - the named clips (and their stems), or every targeted track |
{op:"rippleDelete", ids} | remove clips; later clips on those tracks close the hole |
{op:"closeGap", at} | close the gap under at on every targeted track |
{op:"lift" | "extract", from?, to?} | remove a range (default: inPoint→outPoint, which then clear); extract also closes it |
{op:"insert" | "overwrite", mediaId, at, in?, duration?} | three-point edit: place in→in+duration of the media at at (default: the rest of the media). Insert pushes later clips right; overwrite replaces what is under it. A video brings one linked audio stem per channel. |
{op:"rippleTrim", id, side:"in"|"out", delta} | move a head / tail; everything after follows |
{op:"roll", id, side, delta} | move the cut between two clips; nothing else moves |
{op:"slip", id, delta} · {op:"slide", id, delta} | new source window in place · move a clip between its neighbors |
{op:"crossfade", ids? | at, duration?} | constant-power audio crossfade at the cuts touching ids, or the cut nearest at (handles borrowed from both sides) |
Trims are clamped to the source media, the free room and MIN_DUR; the result note shows the applied delta (… (asked +5, clamped)). Any edit op takes tracks:[…] to target lanes for that op only, and force:true to edit locked material (only when the user asked). A refused op (nothing under at, a locked clip, a media window past the file's end) aborts the whole patch unsaved. setProject also sets inPoint / outPoint (null clears).
Example - cut the 2 s from 10 s to 12 s out of the edit, then crossfade the new cut: {ops:[{op:"extract", from:10, to:12}, {op:"crossfade", at:10, duration:0.5}]}
fablecut_set_project is conflict-checked. The MCP server remembers the revision from the most recent fablecut_get_project call. If project.json has been written by anyone else since that read (e.g. the user dragged a clip in the UI), fablecut_set_project refuses with a "CONFLICT - not saved" error instead of overwriting. Protocol:
fablecut_get_project→ read the document and note itsrevision.- Apply your edits in memory, bump
revision. fablecut_set_project→ if it succeeds you're done.- On conflict: call
fablecut_get_projectagain to get the latest document, re-apply your intended changes on top of it, bumprevision, and callfablecut_set_projectagain.
Pass force: true to fablecut_set_project only when the user explicitly asks to overwrite conflicting changes. fablecut_import_media only appends a new media entry and always merges safely - no conflict check needed.
For Claude Desktop, add to its MCP config: {"mcpServers":{"fablecut":{"command":"npx","args":["-y","fablecut","mcp"]}}} (or "command":"node","args":["<path-to>/fablecut/mcp-server.js"] from a clone) Direct file editing of project.json (below) works too and is equivalent.
Installing as a Claude Code plugin (/plugin marketplace add ronak-create/FableCut, then /plugin install fablecut@fablecut) does the registration for you.
Where the files are#
project.json, projects/, media/, exports/, analysis/ and library/ normally sit in the repo next to server.js; installed from npm (npx fablecut) they sit in ~/FableCut. Set FABLECUT_DATA_DIR to move all six somewhere else; the code and the static app files stay in the install directory either way. The plugin sets this so a plugin update can replace the install directory without touching anyone's timeline or footage. Don't assume project.json is beside mcp-server.js - call fablecut_status, which reports the real paths.
Tests (and nothing else) may set FABLECUT_NO_FS_WATCH=1 to skip fs.watch. On Windows, libuv can abort the process when a file is created under a temp data dir. Production leaves watching on so the UI live-reloads. Tests may also set FABLECUT_NO_AUTOSTART=1 so ensureUIServer() reports a missing editor server instead of spawning one - the auto-start is detached, so a test that triggers it would leave a server.js running after it finishes.