Skip to main content
Glama

dcc-mcp-capcut

Typed MCP adapter for CapCut Desktop. CapCut has no stable public Python API, so this adapter uses a localhost, token-authenticated bridge and a bundled CapCut-side panel. MCP calls remain typed and auditable; the panel is the only component allowed to invoke CapCut host APIs.

Host platforms

Host binding is dispatched through a platform provider rather than hard-coded Windows paths:

Platform

Discovery

Install plan

Doctor capcut_executable

Windows

CapCut.exe / JianyingPro.exe under %LOCALAPPDATA%\<app>\Apps and %PROGRAMFILES%\<app>, with the file version read from the .exe's VS_VERSIONINFO resource

exact winget install command

ok / fail

macOS

CapCut.app / JianyingPro.app under /Applications and ~/Applications, with the Info.plist bundle version

brew install --cask capcut, or the official download page where no cask exists

ok / fail

Linux

none

none; status: unsupported with the reason

skip, reported as unsupported

Each provider also reports the host build it found -- the Windows .exe version resource, the macOS Info.plist bundle version -- and grades it against the machine-readable matrix in src/dcc_mcp_capcut/hosts/versions.py. A build that was read but is unlisted is a warn naming the version, never a silent pass; a build whose version could not be read is a separate warn, because "could not read" and "read and untested" are different facts.

ByteDance publishes no official Linux client, so Linux reports an explicit conclusion instead of an empty "not installed" that could be mistaken for a broken install. macOS window binding needs Accessibility permission for the controlling app (System Settings > Privacy & Security > Accessibility) — a user-side grant the adapter reports but never requests or bypasses. No provider installs anything: every plan still goes through the operator-owned ui_control__system_operation grant.

Related MCP server: CapCut MCP

Capabilities

The bundled skills cover project lifecycle/settings, media import/relink and proxies, timeline/clip editing, transitions, text and auto-captions, audio mixing/fades, effects and color, AI helpers (background removal/stabilization), video/thumbnail export, one-call assembly of a whole edit plan, batch production from a template, and a complete build_vlog_demo recipe.

Export receipt

export_thumbnail and get_export_status accept an opt-in verify_output flag. Left off, a result proves only that the job was accepted — the contract every caller has today. Set it and the host must probe the rendered artifact and return path, exists, size_bytes, duration_sec (timed media only; a still omits it) and streams under verification.output, or the call fails closed. The asynchronous submits export_video and build_vlog_demo do not take the flag: they return a job acknowledgement and the artifact does not exist yet, so their receipt comes from get_export_status. Batch delivery reports one receipt per rendered item and reuses the same field set. The normative table lives in the export and verification reference.

Run locally

uv sync --extra dev
uv run pytest
uv run python -c "from dcc_mcp_capcut import start_server; start_server()"

Guided installation and environment setup

The capcut-setup skill is built in. Call detect_installation and then installation_plan when CapCut is missing. auto_setup_capcut executes the full install-and-bind flow after receiving an operator-owned ui_control__system_operation grant: it installs ByteDance.CapCut when needed, configures the shared runtime/bridge, and verifies the exact CapCut process. It does not inject the panel — that step is operator-owned and consent-gated, and no automatic loader ships with this package (see capcut_panel/LOADING.md). The adapter never shells out, edits the registry, or silently installs software.

Host-bound capabilities additionally need the bundled panel running inside the CapCut process, because the panel is what drains the bridge queue — see src/dcc_mcp_capcut/capcut_panel/LOADING.md for what the panel is, what it requires, and how to confirm it is connected. That guide also records the current limit: this package ships the panel payload but no automatic loader, so injecting it is operator-owned, consent-gated work. Until something polls the bridge, panel_connected stays false and host actions stay queued until the request timeout elapses. Treat panel_connected as a liveness signal for a poller, not as proof that window.CapCut exists: a panel opened in a normal browser also reports true while every host action fails immediately.

Verify with GET /health on the bridge URL, and set DCC_MCP_CAPCUT_BRIDGE_TOKEN to a per-user secret for production use.

Pixel execution (last resort)

CapCut renders as a single opaque QML canvas — the window inventory reports one node with no children, and dcc-cua treats the missing accessibility provider as permanent for the window class — so semantic automation is closed. For what the panel cannot reach, dcc_mcp_capcut.cua drives one typed action as exact PID/HWND binding -> pixel snapshot -> coordinate input -> verify.

It is deliberately the last route, not the first: the bridge and the panel stay the typed, auditable path. Two rules make the route honest:

  • unknown is not success. Verification fails closed when a predicate cannot be evaluated, which is the common case for element predicates on this canvas.

  • A changed pixel is not a verified edit. pixel_changed is reported alongside the verdict, never folded into it: a dialog appearing and the requested edit landing look identical from a pixel digest.

Coordinates are build-specific, so the version guard refuses to run against a build the support matrix does not list unless allow_unverified=True is passed. The route is not headless — unattended means unattended on an interactive Windows or macOS desktop; Linux CI is out of scope. Input is delivered background first and escalates to foreground only on the driver's background_unavailable answer, never pre-emptively.

CapCut also upgrades itself in place (a 9.5.0.4050 launch was observed deleting the 9.4.0.4015 install tree), so snapshot the install tree before launch and diff it afterwards; a host_replaced diff means rebind, because a recycled PID can name a different window.

See src/dcc_mcp_capcut/skills/references/pixel-execution.md for the full contract, and dcc-mcp-capcut-doctor --json for the cua_execution evidence check.

Vlog demo

demo/assets.json records NASA/JPL public-domain source pages and attribution notes. Run python demo/fetch_assets.py, then python demo/render_vlog.py for an offline 9:16 proof in demo/output/free-travel-vlog.mp4. The render is driven by the canonical edit plan and writes it to demo/output/free-travel-vlog.plan.json; feed that file to apply_edit_plan with media_dir set to demo/ for a native CapCut project. That directory is the delivery root the plan's portable relative paths resolve against.

Canonical edit plan

dcc-mcp-capcut/edit-plan/v1 is the one plan document the adapter agrees on, documented in docs/edit-plan.md. compile_edit_plan normalises a plan or a vlog recipe into it, and three links consume it:

Link

Tool

Host needed

Compile/validate

compile_edit_plan

no

Portable export/import

export_otio / import_otio

no

Assemble into CapCut

apply_edit_plan

yes

All three share one set of rules, so a plan that compiles is a plan every link accepts — previously the vlog recipe and the OTIO exporter disagreed about whether two clips on one track could overlap. The one exception is OTIO export, which additionally requires media_duration on every clip because it will not write an available_range it cannot prove.

Assembly is one call instead of a hand-orchestrated sequence: apply_edit_plan takes a plan plus a media directory, validates it host-free, and lowers it to an ordered action script. Use dry_run: true to inspect that script without dispatching anything. See ADR 0002 for the spike behind it.

Batch production from a template

One template plus N variable sets becomes N renders. The template is an ordinary plan or recipe carrying {{placeholder}} fields, so every variant is validated by the same rules as a hand-written plan. render_batch_template takes the two and returns every compiled plan, reframe report and encode preset, host-free:

{
  "template": {
    "schema": "capcut-vlog-recipe/v1",
    "project_name": "promo {{lang}} {{aspect}}",
    "aspect_ratio": "{{aspect}}",
    "output_path": "out/promo_{{lang}}_{{aspect}}.mp4",
    "media": [{"id": "a", "path": "clips/{{lang}}/a.mp4", "start": 0, "duration": "{{length}}"}],
    "reframe": {"fit": "contain", "source_aspect_ratio": "16:9"},
    "export": {"codec": "h264", "bitrate_mbps": 12}
  },
  "variables": [
    {"lang": "en", "aspect": "16:9", "length": 8},
    {"lang": "zh", "aspect": "9:16", "length": 8}
  ]
}

run_batch then assembles and exports each item in turn, writing a manifest after every one and reporting a receipt per delivered item. Failures are isolated to the item that earned them, and a batch is resumable from its manifest with resume: true. Reframing is declared and reported, never applied silently: cover requires a safe_area, and a crop that would eat it is an error rather than a warning.

Rendering is sequential and needs the visible, bound CapCut window for the whole run. See docs/batch-and-templates.md.

Portable OpenTimelineIO export

Install dcc-mcp-capcut[interchange] to enable the capcut-interchange skill's export_otio and import_otio tools, or use the host-independent CLI:

python -m dcc_mcp_capcut.interchange --input edit.json --output timeline.otio

export_otio accepts either that explicit edit decision list or a canonical plan document, which it lowers to the EDL below. The list has name, fps, width, height, duration_frames, and tracks. Each track has name, kind (Video or Audio), and ordered clips. Each clip specifies name, relative media path, timeline start, optional source_in (default 0), duration, and media_duration; all time values are integer frames at fps. Optional captions contain text, start, and duration and become markers. See the interchange contract for a complete example.

The exporter preserves gaps, source trims, separate tracks and fractional frame rates. It rejects overlaps, out-of-range edits, absolute/traversing media paths and unknown fields. The CLI refuses to overwrite an existing file.

This is export from supplied edit decisions, not a readback of a live CapCut project. Bake unsupported effects into media and include SRT for editable subtitles. Ship all referenced media with the OTIO file, and resolve relative paths from its directory. Other applications may require an OTIO importer.

import_otio reads OTIO JSON or an .otio file back into a canonical plan. Timings, trims, gaps, track structure and caption markers survive; advisory presentation fields (audio volume/fades, caption style) have no OTIO representation and are reported as dropped rather than reconstructed.

Preflight diagnostics

dcc-mcp-capcut-doctor is a read-only preflight entry point. It collects the evidence the adapter needs before it binds a window, so a failed start reports one diagnosable cause plus a remediation instead of a traceback:

dcc-mcp-capcut-doctor             # human-readable summary
dcc-mcp-capcut-doctor --fix-hints # add remediation steps
dcc-mcp-capcut-doctor --json      # machine-readable report

It checks the Python version, dcc_mcp_core against the CI-verified floor, the runtime bundle handshake, the CapCut executable, dcc-cua availability and window uniqueness, the bridge port and token, the bundled panel payload, the optional Qt probe configuration, and opentimelineio. The executable and window checks run through the platform provider, so macOS gets real host verdicts and Linux gets an explicit unsupported reason. The report names the provider in host_provider.

Every check is ok, warn (the adapter still starts, but degraded or with an optional feature disabled), fail (the adapter cannot start in this state), or skip (not applicable to this platform). The exit code is 0 when nothing failed and 1 when at least one check failed. The doctor never installs, writes, or mutates anything.

Release gates

Release Please cuts the release: it opens a release PR against main, and merging it creates the tag, the GitHub release, and the artifacts. Two gates stand between a drifted tree and a published artifact.

One version per release. The wheel and sdist take their version from pyproject.toml while the panel archive takes the one release.yml derives from the tag, so a tree that lagged behind its own tag used to publish a single release mixing dcc_mcp_capcut-0.1.0-*.whl with dcc-mcp-capcut-0.2.0-panel.zip. tools/check_release_version.py reads the version back out of pyproject.toml, src/dcc_mcp_capcut/__version__.py, the wheel, the sdist and the panel archive, and fails unless every one of them declares the version the release tag names:

python tools/check_release_version.py                      # do the sources agree?
python tools/check_release_version.py --print-version      # the in-tree version
python tools/check_release_version.py --expect 0.3.0 --dist dist

It runs on every PR that builds artifacts, and twice in release.yml — once against the tree before anything is built, and once against dist/ before anything is uploaded. A file it cannot read a version from is a failure, not a skip.

A green release PR. A PR opened with the default GITHUB_TOKEN creates its pull_request runs in an approval-required state, so they sit at action_required with zero jobs and never turn green on their own. release-please.yml therefore prefers secrets.RELEASE_PLEASE_TOKEN — a PAT makes the release PR an ordinary PR whose checks run on their own — and falls back to GITHUB_TOKEN until that secret exists. With the fallback in place, approve the release PR's runs by hand on the Actions page before merging; the version gate in release.yml runs regardless, because it is triggered by the push to main.

Runtime boundary

The adapter is an external-bridge (instance_type=gui) service. It does not invent a CapCut API, use raw script execution, or silently fall back to generic computer automation. For UI verification use the project-owned dcc-cua / ui-control route with an exact CapCut PID and HWND.

dcc-mcp-runtime is distributed as a separately verified runtime bundle, not as a PyPI dependency. The dcc-mcp-capcut-runtime entry point refuses to start when that runtime is missing, its CapCut manifest is stale, or required handshake metadata is absent.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants like Claude Code to build and edit CapCut video projects locally by creating drafts, adding media and effects, and saving projects that open in CapCut desktop.
    1
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents and MCP clients to programmatically edit video projects on a local desktop editor, with 119 tools for multitrack editing, effects, captions, audio, and batch auto-editing, producing reviewable and reversible real timeline edits.
    AGPL 3.0