recreator-mcp
MCP server exposing recreator-core operations as typed tools.
A thin protocol wrapper: schema translation only, zero editing logic. Every tool
translates JSON arguments into a recreator-core call and translates the result
back. No transcription, no detection, no thresholds, no editorial judgment —
the calling agent decides what to cut, caption, or animate and passes explicit
ranges and values. Core remains fully usable as a library and CLI without this
package installed.
Boundary conventions
- All times are seconds (floating point) at the tool boundary. Core's
microsecond
Timetype is constructed at the edge. - Ranges are
[[start, end], ...]pairs, e.g.[[12.4, 15.9]]. - Keyframe offsets are measured from the start of the clip, not the timeline.
.recreator/project.jsonis the source of truth and the default for every tool'sprojectargument. Tools load it, apply the op, and save atomically (write to a temporary file in the same directory, then rename).- A CapCut draft is a build artifact, produced only by
recreator.export.capcut.
Tools
| Tool | Purpose |
|---|---|
recreator.project.load |
Read timeline state; source of clip ids |
recreator.project.save |
Update project name and metadata |
recreator.probe.media |
ffprobe duration, resolution, streams |
recreator.probe.loudness |
Windowed RMS/peak dB series (raw measurements only) |
recreator.timeline.init_from_media |
Construct a timeline seeded from probed media files |
recreator.timeline.remove_ranges |
Delete ranges, rippling by default |
recreator.timeline.keep_ranges |
Keep only the given ranges |
recreator.timeline.mute_ranges |
Silence ranges without changing timing |
recreator.timeline.bleep_ranges |
Censor ranges: mute, bleep, or cut |
recreator.timeline.add_text_clips |
Add caption/text clips with character-offset styles |
recreator.timeline.add_media_clips |
Add b-roll, overlays, music |
recreator.timeline.add_keyframes |
Animate one property of one clip |
recreator.timeline.set_transform |
Static scale/position/rotation/opacity/crop |
recreator.export.capcut |
Build a CapCut draft into a caller-specified directory |
recreator.validate |
Report structural issues before exporting |
recreator.catalog.list |
Browse every tool this server exposes, optionally by namespace |
recreator.catalog.get |
Full parameter schema, return shape, and example for one tool id |
recreator.templates.list |
Browse HyperFrames route templates, optionally filtered by a query substring |
recreator.templates.get |
Full contract for one route: input, output, triggers, interview shape, pre-fill table |
recreator.templates.recommend |
Rank routes against a free-text prompt by counted lexical evidence |
recreator.jobs.submit |
Submit a registered async job kind; optionally block until done |
recreator.jobs.get |
Read one job's current record, non-blocking |
recreator.jobs.wait |
Rejoin an existing job and block until terminal |
recreator.jobs.list |
List recent jobs, newest first, optionally filtered by status |
recreator.jobs.cancel |
Request cooperative cancellation of a queued or running job |
Async jobs
Every tool above is fast and synchronous — this is not that. It is scaffolding
for future generative-media operations (b-roll generation, TTS, image gen,
cloud STT) that call out to paid third-party APIs and genuinely take minutes,
mirroring the shape of Higgsfield's async job API (generate create --wait,
generate get, generate wait).
A job kind is a plain Python callable registered by name in
recreator_mcp.jobs; the runner itself is pure mechanism (dispatch,
persistence, status) and knows nothing about what any kind does. Registering a
future kind — e.g. broll.generate — is one call to register_kind, with no
changes to the runner.
One real kind ships today: probe.batch, which probes N media files via
core's probe_media and reports progress as it goes. Each probe is a real
ffprobe subprocess, so it earns async honestly for large batches.
recreator.jobs.submit(kind="probe.batch", params={"paths": [...]})
-> {job_id, kind, status: "queued", submitted_at}
recreator.jobs.submit(kind="probe.batch", params={"paths": [...]}, wait=true)
-> full record with status: "completed" and result: [...]
recreator.jobs.wait(job_id="...") # rejoin, block until terminal
recreator.jobs.get(job_id="...") # non-blocking status check
recreator.jobs.list(status="running") # recent jobs, newest first
recreator.jobs.cancel(job_id="...") # cooperative — stops between units
Job record: job_id, kind, status (queued / running / completed
/ failed / cancelled), params, submitted_at, started_at,
completed_at, progress (0.0–1.0), message, result (on success),
error (on failure).
Persistence: jobs live under .recreator/jobs/, one JSON file per job,
written atomically the same way as project.json (tempfile in the same
directory, fsync, then Path.replace). A job submitted in one MCP session
is visible to recreator.jobs.get/recreator.jobs.wait in a later one — that
cross-session rejoin is the entire point of the job/status split. Cancelling a
job from a session that did not submit it is rejected with an actionable error
rather than silently doing nothing, since there is no in-process handle to
signal.
Cancellation is cooperative: a job kind calls cancel.raise_if_cancelled()
between units of work (e.g. between files in a batch), so it takes effect at a
clean boundary rather than killing a thread mid-unit.
Catalog
recreator.catalog.list and recreator.catalog.get let an agent discover
operations and their parameters at runtime instead of a skill hardcoding them
in markdown — the same affordance as higgsfield model list / model get.
The catalog is generated by introspecting the live registered tools
(mcp.list_tools()), so it can never drift from the actual tool set; only
each tool's namespace and whether it mutates project.json are curated,
everything else (params, types, defaults, descriptions) is reflected straight
from the tool's JSON input schema.
recreator.catalog.list # every tool, grouped
recreator.catalog.list(namespace="timeline") # one namespace
recreator.catalog.get(id="recreator.timeline.remove_ranges") # full schema
Templates
recreator.templates.list and recreator.templates.get expose the ten
HyperFrames route templates — the same affordance as higgsfield preset list /
preset resolve, so an agent can browse and resolve a route contract at
runtime instead of a skill hardcoding it in markdown. Both tools parse
skills/recreator/hyperframes/references/routes/*.md directly (never
hand-copied) and cache the result in-process, keyed by the routes directory's
mtime so edits on disk are picked up without a restart.
recreator.templates.list # all 10 routes
recreator.templates.list(query="launch") # substring filter
recreator.templates.get(id="talking-head-recut") # full contract
recreator.templates.recommend occupies the slot where Higgsfield has
generate cost — re:Creator has no metered spend to estimate, so it
recommends a route instead. Its ranking is counted, attributable lexical
evidence only: a trigger phrase found verbatim in the prompt, a keyword
overlap with a route's input/output text, or an exact production_type row
hit from production-map.md. Candidates are ordered lexicographically by the
per-kind evidence count, most specific signal first — trigger_phrase (a
route author's own curated wording) outranks production_type_row (an exact
lookup-table hit), which outranks keyword_overlap (incidental shared words)
— because a curated phrase is structurally a stronger match than a stray
common word, not because of any tuned weight: nothing is scaled or
multiplied, only compared count-by-count. score is the total evidence
count; signals is the {trigger_phrase, production_type_row, keyword_overlap} breakdown the ordering is actually computed from, so a
caller can reproduce the ranking by hand from what is returned. Ties break on
route id for byte-stable output. This is measurement in the sense CLAUDE.md
allows, not a recommendation engine: it returns ranked candidates plus a
note, never a single verdict, and an empty list rather than a fallback
guess when nothing matches.
recreator.templates.recommend(prompt="turn this website into a video")
-> {
candidates: [
{
route: "product-launch-video",
score: 3,
signals: {trigger_phrase: 1, production_type_row: 0, keyword_overlap: 2},
evidence: [
{kind: "trigger_phrase", detail: "turn this website into a video"},
{kind: "keyword_overlap:input", detail: "website"},
{kind: "keyword_overlap:output", detail: "video"}
],
contract_summary: "A product promo, launch video, site tour, or showcase MP4."
},
...
],
note: "Ranking orders candidates by counted evidence, most specific signal first ..."
}
The routes directory is located relative to the installed package or by
walking up from the current working directory to the repo root; set
RECREATOR_SKILLS_DIR to override it explicitly (e.g. under HTTP transport
running from an unrelated cwd). RECREATOR_MCP_* is reserved for transport and
auth configuration, not this lookup.
HyperFrames template registry
The recreator.hyperframes.templates.* tools front a Cloudflare Worker that
serves the mirrored HyperFrames template catalog (metadata in D1, source zips in
R2). The worker is the only source: there is no local-file fallback and no
direct call to hyperframes.dev or static.heygen.ai, so an agent is never
uncertain which registry answered it.
recreator.hyperframes.templates.tags()
recreator.hyperframes.templates.list(aspect="9:16", type="block", tags=["ad-template"])
recreator.hyperframes.templates.get(name="ai-chat-reveal")
recreator.hyperframes.templates.pull(name="ai-chat-reveal", dest="./templates/hero")
Discovery runs widest to narrowest. tags publishes the vocabulary a filter may
use, each with its template count; list filters; get returns everything
known about the few finalists chosen.
list filters on hard facts only — aspect, type, tags (ANDed),
min_duration, max_duration, origin, limit — and returns the worker's
candidates in the worker's order (name ASC), untouched. It does no scoring, no
relevance sort, and picks no winner: the response carries a ranking note
saying so. Which few templates are worth showing the user, and in what order, is
the agent's judgment — it has the brief, the footage, and the user's taste, and
the registry has none of those. This is the same boundary
recreator.templates.recommend respects, taken one step further: here there is
not even counted lexical evidence to report, because filtering on facts is all
the registry is asked to do.
Each candidate carries six fields — name, title, type, aspect,
duration_s, tags — and nothing else. The description, dimensions,
provenance and raw_tags live on get, which is called on a handful of
finalists rather than on all 380; on the live registry the former full shape was
143.7 KB against 54.1 KB now, and description alone was half of it.
Only canonical tags filter — those carried by at least 8 templates. An
unknown or merely-rare tag is rejected with near matches and the canonical
tag it folds into, never answered with an empty list, because an empty list
reads as "no such template exists" and sends an agent away from templates that
were there all along. Call tags when unsure. Filtering on a concrete aspect
also returns aspect-agnostic (any) templates, which adopt whatever canvas
hosts them — 219 of 380 are such components, and excluding them would hide most
of the usable pieces.
pull is the one tool in this namespace that writes to disk, so it is
deliberately cautious:
- It refuses a non-empty destination unless
overwrite=true, the same consent rule the CapCut install path follows. - It rejects the whole archive if any member would write outside the
destination — a
../path, an absolute path, or a symlink — before extracting a single byte, so a rejected pull leaves nothing half-unpacked. - A template whose source lives with the HyperFrames CLI rather than in R2
raises with the exact
npx hyperframes catalog --query "<name>"command to run instead. That is a different install path, not a failure.
Presigned R2 download URLs are short-lived (300s TTL) and single-use. Where this client mints one itself it is followed once, immediately, and never logged or written to disk.
pull without a registry credential
Downloading a template is two steps with different credential needs. Minting
the presigned URL requires the registry bearer token — a server-side
credential. Fetching that URL requires nothing at all. So pull splits them:
recreator.registry.get_source_url(name="ai-chat-reveal") # remote MCP, holds the token
-> { name, url, bytes, expires_in_seconds: 300 }
recreator.hyperframes.templates.pull( # here, no credential read
name="ai-chat-reveal",
dest="./templates/hero",
source_url="https://...", # from the call above
declared_bytes=48213, # its `bytes`, for the size check
)
With source_url supplied, resolve_registry_config() is never called and
neither RECREATOR_REGISTRY_URL nor RECREATOR_REGISTRY_TOKEN is read. The URL
is the whole authorization. It must be https:// — a caller-supplied URL
fetched blind is an SSRF vector, so anything else is refused outright. Because
the URL expires after 300s and is single-use, a failed download means mint a
fresh one, not retry the same one.
Omit source_url and pull resolves the URL itself, exactly as before. That
path still needs the credential locally, and is the one to use when you are
developing against the registry directly.
| Flag | Env var | Default |
|---|---|---|
--registry-url |
RECREATOR_REGISTRY_URL |
none — required |
--registry-token-file |
RECREATOR_REGISTRY_TOKEN |
none — required |
Both halves are required for every tool in the namespace except a pull given a
source_url, and are resolved CLI flag > env var, inside the call. With either
missing, those tools raise a ToolError naming both environment variables; they
never fall back silently. The base URL must be https:// — the client refuses
to put a bearer token on a plaintext request.
RECREATOR_REGISTRY_URL=https://<worker>.workers.dev \
RECREATOR_REGISTRY_TOKEN=<token> recreator-mcp
Safety
recreator.export.capcut writes to the out_dir you specify and never defaults
anywhere inside CapCut's own library. Passing install=true additionally copies
the built draft into the user's real CapCut drafts folder, where CapCut will
show it as a project; that step needs overwrite=true to replace an existing
draft of the same name. Leave install off unless the user asked for it.
Configuration
Claude Code — .mcp.json
Place at the repository root (or merge into an existing .mcp.json):
{
"mcpServers": {
"recreator": {
"command": "uv",
"args": ["run", "--package", "recreator-mcp", "recreator-mcp"],
"cwd": "/absolute/path/to/ReCreator"
}
}
}
Codex — ~/.codex/config.toml
[mcp_servers.recreator]
command = "uv"
args = ["run", "--package", "recreator-mcp", "recreator-mcp"]
cwd = "/absolute/path/to/ReCreator"
Replace /absolute/path/to/ReCreator with the workspace root. Both hosts launch
the server over stdio. If the package is installed into an environment already on
PATH, command = "recreator-mcp" with no args works as well.
Install
From the workspace root:
uv sync --all-packages --all-groups
uv sync --all-groups alone installs only the root dev group, which does not
include this package; --all-packages is what puts recreator-mcp and the mcp
SDK into the environment.
CLI fallback
Hosts without MCP support reach the same operations through recreator-core's
CLI, which needs no MCP installed:
recreator project init --media <path>
recreator probe <path>
recreator timeline remove-ranges --ranges '[[12.4,15.9]]'
recreator export capcut --draft-name "my-cut"
The effect on project.json is identical either way.
Transports
recreator-mcp runs over stdio (default, unchanged) or streamable HTTP.
Local files stay local either way: HTTP mode does not move project.json or
media off the machine the server runs on, it just changes how a client talks
to the same server.
recreator-mcp # stdio, exactly as before
recreator-mcp --transport http # HTTP, 127.0.0.1:8000/mcp
recreator-mcp --transport http --host 0.0.0.0 --port 9000 --path /recreator
| Flag | Env var | Default |
|---|---|---|
--transport {stdio,http} |
RECREATOR_MCP_TRANSPORT |
stdio |
--host |
RECREATOR_MCP_HOST |
127.0.0.1 |
--port |
RECREATOR_MCP_PORT |
8000 |
--path |
RECREATOR_MCP_PATH |
/mcp |
CLI flags win over environment variables, which win over the defaults above.
Binding 0.0.0.0 is never the default — it takes an explicit --host 0.0.0.0
or RECREATOR_MCP_HOST=0.0.0.0.
Authentication
HTTP mode requires a bearer token; stdio needs none (the host process already
controls who can talk to it). Set the token via RECREATOR_MCP_TOKEN or
--token-file <path>, then send it as Authorization: Bearer <token>.
RECREATOR_MCP_TOKEN=$(openssl rand -hex 32) recreator-mcp --transport http
Starting --transport http with no token configured refuses to start:
Refusing to start HTTP transport without a bearer token: an unauthenticated
HTTP MCP server would expose filesystem-touching tools to the network.
Fix it by either setting RECREATOR_MCP_TOKEN (or passing --token-file), or, if
you understand the risk and are only binding to a trusted network, pass
--insecure.
Pass --insecure only for a deliberately trusted network (e.g. 127.0.0.1
loopback during local development) — it accepts every request unauthenticated
and prints a warning on startup. Token comparison uses a constant-time compare
so a network attacker cannot recover the token by timing responses.
This is a static pre-shared token, not full OAuth. It is not the token-based
auth Higgsfield's hosted MCP server uses against a user account — there is no
provisioning, rotation, or per-client identity. The verifier is structured so
a real OAuthAuthorizationServerProvider can replace it later without
reshaping how transports are wired up.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file recreator_mcp-0.1.2.tar.gz.
File metadata
- Download URL: recreator_mcp-0.1.2.tar.gz
- Upload date:
- Size: 51.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d43ee0d080c4fd4dc58f62532f8f6d368f979c39604edd96572312615b8f1df
|
|
| MD5 |
54b54c561f1e296f4596586c6d759259
|
|
| BLAKE2b-256 |
b01c3afc946ed1dee33947021dff7d02aeab9cbd5355cc64512542c19a38fe89
|
File details
Details for the file recreator_mcp-0.1.2-py3-none-any.whl.
File metadata
- Download URL: recreator_mcp-0.1.2-py3-none-any.whl
- Upload date:
- Size: 47.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b6c67ac074bd1a2e66f094a3d55256cb8069b2fb7ee7afbab4050a3ca8d05e6
|
|
| MD5 |
b934390d61885cbc35006ce24d6acbc8
|
|
| BLAKE2b-256 |
01675bd063761c710e364945393ce2d2d089082c31c8062feefd9c54c1f43973
|