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:

Token-efficient editing important for agents#

Editing via full get→modify→set costs thousands of tokens per change. Cheaper:

  1. Plan from fablecut_get_project {compact:true} (≈10× smaller than the JSON) and fablecut_status - fetch the full JSON only to inspect exact keyframes.
  2. Edit with fablecut_patch_project ops - 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, pass baseRevision (the revision you planned from - fablecut_get_project, fablecut_status or 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.
  3. Docs: request fablecut_docs {section:"schema"} (or Recipes, "Remake", …) instead of the whole manual; skip it entirely if the schema is already in context.
  4. 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.
  5. Batch related changes into ONE patch call (ops apply in order, one revision bump).
  6. Cut and trim with the edit ops (next section) rather than recomputing start / in / duration by 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.

opdoes
{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:

  1. fablecut_get_project → read the document and note its revision.
  2. Apply your edits in memory, bump revision.
  3. fablecut_set_project → if it succeeds you're done.
  4. On conflict: call fablecut_get_project again to get the latest document, re-apply your intended changes on top of it, bump revision, and call fablecut_set_project again.

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.