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.
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:
unknownis 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_changedis 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.
Release files for dcc-mcp-capcut 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| dcc_mcp_capcut-0.5.0.tar.gz | 351.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dcc_mcp_capcut-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 614.0 kB
Release files / dcc_mcp_capcut-0.5.0.tar.gz
| Download URL | dcc_mcp_capcut-0.5.0.tar.gz |
|---|---|
| Size | 351.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
117708342d684e8914aac3445de56a43f342f5a1e6efd2c7eaa1ef0249f723a3
|
|
BLAKE2b-256 checksum How to use checksums |
3ec3ba118b227378fc8c2e7738bd93daef598910d61d688a7940e18fc01f863c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency logRelease files / dcc_mcp_capcut-0.5.0-py3-none-any.whl
| Download URL | dcc_mcp_capcut-0.5.0-py3-none-any.whl |
|---|---|
| Size | 262.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ffdfb6020dae9b07982c4593b0d63912d1439711caac4179687747b6b5de1122
|
|
BLAKE2b-256 checksum How to use checksums |
e579940c7f086e6061677084e093cd543ca2371da717d56a1ed0635676221823
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 27, 2026.
Transparency log