ghostfeed-ugc-reactions
Create UGC reaction videos over MCP the way the dashboard does. Get a template, render an avatar into the source pose (the first frame), get the user to approve that frame, then animate it. Two families, clone the reference motion or a prompt. Refined from real founder dogfooding.
The tools enforce the hard rules server-side: every write is workspace-scoped, credits are charged and reported for you, the duration limits are applied on import, and the flow is fixed at two phases (frame, then video). This skill makes you read the user's intent and run the flow well on top, and it makes the frame a real checkpoint.
This is a thin remote control for the dashboard flow, not a new engine. You do not reinvent anything. You quicken the click path over chat: pick a template, render the frame, get a yes, animate it.
Library-first casting
For every reaction request, prefer Ghostfeed's reusable clip libraries before making a custom first frame. Follow this order unless the user explicitly names or supplies an exact source:
- Search
list_reaction_templatesfor a suitable opening pose in the workspace's owned and stock templates. Judge candidates byopensOn, then usemotiononly to decide whether the source performance is also useful. - If no template is suitable, search
list_inspiration_reactionsthe same way. - Only after both libraries have no reasonable opening-pose match, fall back to
referenceImageUrlfrom an avatar photo, user reference, or external image. Tell the user briefly what the library lacked and why a custom frame is necessary.
A custom motion request does not imply a custom first frame. A template or
inspiration can supply only the opening composition while a prompt mode animates
the approved frame from the user's words. Do not paste the library clip's
motion into the video prompt unless the user asked to reproduce that motion.
Do not search the web or browse avatar photos for a source until the two
Ghostfeed libraries have been checked.
The flow
-
Orient a new user before choosing a source. Explain the two phases in plain language: first Ghostfeed makes a cheap still frame for approval; only then does it spend on motion. Also explain the two motion choices: 1:1 clone follows the source video's performance and takes no motion prompt; prompt-directed uses the source only for the opening composition and follows words the user approves. Ask which outcome they want instead of assuming they know the distinction.
-
Get a source, following the library-first order above. A template is a source motion clip. Four ways in:
- Use one that exists:
list_reaction_templates(stock plus the workspace's own). You cannot watch the motion, so if the user must choose, describe the options from name and category, do not pretend to have seen them. - Import from a link:
import_reaction_templatewithsourceUrl(a TikTok or Instagram link). - Import a local file:
request_reaction_uploadreturns a short-liveduploadUrl. PUT the raw file bytes to it with the given Content-Type (for examplecurl -T clip.mp4 -H "Content-Type: video/mp4" "<uploadUrl>"), thenimport_reaction_templatewithuploadedFileUrlset to the returnedfileUrl. - Reuse an inspiration clip:
list_inspiration_reactions, then pass its id togenerate_reaction_framesorimport_reaction_template. It is auto-saved to the workspace as a template first and the new template id is surfaced. Import is async and free. Pollget_generationuntil terminal.succeededmeans ready.needs_actionmeans the source is 30 to 120 seconds and must be cropped before generation. Offercrop_reaction_templatefor an approved exact range orsmart_crop_reaction_templatefor scene-boundary splitting. In an MCP Apps host, callrender_source_cropfirst so the user trims visually on a filmstrip; its buttons send the crop request into the chat. Over 120 seconds is rejected.
- Use one that exists:
-
Look for a reusable first frame before generating one. Once both the avatar and template are known, call
list_reaction_frameswith bothavatarandtemplateId. A different motion prompt, video model, duration, resolution, or audio setting does not require a different still. If candidates exist, show the best few newest-first and offer to reuse one for free or make a fresh variation. Never silently reuse a frame merely because its ids match: an older take may have been rejected, use a different wardrobe, or predate a source edit.preferenceStatusis useful evidence, not a substitute for the user's approval. A frame withanimationStatus: "complete"is still reusable for another video. Clone modes require a frame with template lineage; prompt modes may animate a source-less frame. -
If the user does not choose a reusable frame, render one.
generate_reaction_frameswith the source (atemplateId, aninspirationId, or areferenceImageUrl) and one or moreavatars(names or ids, up to 10). It makes ONE frame per avatar. Poll each returned generation untilsucceeded. The frame image isoutput.urlandoutput.idis theframeIdyou carry into the video phase. It usesgemini_flashunless the user asks for another model (list_image_models). Before calling it, state: “First frame: Gemini Flash, standard resolution” (substitute the actual selected values) and tell the user they can ask for a different listed image model or 1080p. Keep this to one sentence unless they ask for options. -
Get the frame approved. This is the milestone, treat it as a hard stop. Show the user the rendered frame or frames and get an explicit yes that the avatar looks right BEFORE any video. The frame costs a fraction of a video, so this is where you catch a bad render cheaply. If a frame is off,
regenerate_reaction_framewith that generation's id for a fresh take. Never start a video on a frame the user has not approved. Present every succeeded frame in the approval reply per Delivering assets in chat below. -
Prepare and approve the motion settings. Call
list_reaction_video_modesbefore the first video in a conversation. State the selected mode/model, duration behavior, output resolution, audio behavior, and approximate per-second price. If values were omitted, label them as defaults and say the user can ask for another listed mode. For prompt mode, show the exact prompt in a fenced block, after trimming surrounding whitespace, and ask for explicit approval. Do not paraphrase it in the approval message. For clone mode, say clearly that no prompt will be sent because motion comes from the source. MiniMax H3 has three prompt profiles:prompt_based,audio_guided, andvideo_guided. All three use the approved first frame. For either guided profile, callgenerate_reaction_promptwith the matchingpromptProfile,outputDurationSeconds, and resolution. Show its exact prompt, warning, and cost plan. The complete guided prompt contains visibleMandatory reference guidanceandAction guidancesections. Tell the user not to change the mandatory section and to make only small changes inside the action section. Ghostfeed sends this approved prompt without adding hidden prompt text. Pass its returnedtemplateIdassourceTemplateIdin the video call. -
Animate the approved frame.
generate_reaction_videowith the approvedframeIdsand amode. For prompt mode, passpromptApproved: trueonly after the prompt approval in step 6. Poll each generation until its state is terminal:succeeded,failedorcanceled. The clip isoutput.url. (The board inlist_reaction_videoscalls the same finished statecomplete; a generation never reportscomplete, so a client waiting for that word waits forever.) Most videos finish in a few minutes. Poll the same generation for up to 25 minutes; if it is still running, return its exactdashboardUrl—never resubmit. For MiniMax H3, also pass the matchingminimaxH3Mode. Guided calls require the source template id, explicit duration, and complete prompt used for approval. Do not remove or rewrite its mandatory reference section. Use a protectivemaxCreditsfrom the returned H3 cost plan. -
Hand over. Present every completed video per Delivering assets in chat, and report the spend and the exact generation-specific dashboard link (see Money and link).
Two ways to recreate a source performance
When a user says “clone,” make the distinction explicit. They have two valid paths after the first-frame checkpoint:
- Exact motion-control clone. Use
one_to_one_standard(orone_to_one_clone_premium) when they want the closest possible one-to-one reproduction of the source performance. It needs a frame made from that template, takes no prompt, and follows the source clip's duration. - Prompt-directed recreation. Generate the first frame with any supported
image model, get it approved, then animate it in a prompt mode (Seedance,
Grok, PixVerse, or Kling). The caller may supply any motion prompt and choose
a supported duration. If they do not want to write one, call
get_reaction_templatewith the sourcetemplateId. The stored prompt istemplate.motionAnalysis.prompt; pass that value togenerate_reaction_videoonly whenmotionAnalysis.statusiscomplete. - MiniMax H3 guided recreation. Use an approved frame from the same source
template.
audio_guidedtransfers speech, vocal timing, and visible performance from source audio at no input charge.video_guidedtransfers the complete source motion and exact edit structure, and adds the billed source-video input. Generate and approve the matching guided prompt before video submission.
Use the first path for faithful motion replication and the second when the
user wants to preserve the source's overall movement while changing duration,
model, or creative direction. Do not paste the short motion field from a list
result into generate_reaction_video; it is only a browsing summary. The full
analysed prompt is motionAnalysis.prompt on get_reaction_template. Omitting
mode defaults to the prompt family (seedance_2_0_fast), so name a clone mode
explicitly when exact motion control is intended.
Prompt approval is a separate checkpoint
For a prompt-directed recreation, show the exact proposed generation prompt in
a fenced block. When it came from source analysis, also show
motionAnalysis.timeline separately as explanatory timing. Ask whether they
approve the exact prompt and timing, or want either changed. Do not pass a prompt
to generate_reaction_video until the user explicitly approves it, and never
set promptApproved: true speculatively.
This does not replace first-frame approval. The rendered frame and the exact prompt are separate approvals; whichever is prepared first, do not start the paid video generation until the user has explicitly approved both.
Where the analysis lives
The import pipeline attempts to analyse each source automatically, but an
analysis can be missing while a job is queued, retried, or has failed. The
canonical place to read it is the free get_reaction_template response:
template.motionAnalysis.status
template.motionAnalysis.prompt
template.motionAnalysis.timelineUse template.motionAnalysis.prompt only when its status is complete.
Call generate_reaction_prompt only as a repair:
- when
motionAnalysisis absent; - when its
statusis notcomplete; or - when the source was re-cropped or otherwise changed after the analysis was written.
It is a write tool and can spend on a vision call, so do not reach for it before checking the free read.
The same template response also carries the source descriptions:
opensOnis the opening still: camera distance, gaze, hands, light. Prompt mode obeys the picture more than the words, so this is what you are really choosing when you pick a clip.motionAnalysisis the timed motion across the whole clip.
list_reaction_templates and list_inspiration_reactions carry both shortened
to one browsing line. get_reaction_template carries both in full.
Reusing existing work
list_reaction_frames is the first-frame inventory. For a known avatar and
template, call it with both filters before generate_reaction_frames. Show
candidate images and ask whether to reuse one or create a fresh take. Reuse
costs no image credits. Pass the chosen existing frameId directly to
generate_reaction_video; the same frame can produce any number of videos with
different approved prompts and settings.
list_reaction_videos is the compact inventory of existing renders. Rows carry
avatarId but not the avatar name, so resolve names with list_avatars when you
are reporting a board back to the user. A row's videoUrl is the canonical clip,
which is the post-edit version once one exists. A row's
sourceReactionId identifies the canonical source template; call
get_reaction_template to read that source clip, its import/original URL, its
full opensOn still description, and its cached motion analysis. Call
get_reaction_video only when you need the actual prompt and resolved settings
used for a particular generated render. The template analysis is a starting
point: for prompt-mode generation, use the motion the user actually asks for and
pass that as prompt.
list_reaction_video_modes has every mode with its per-second cost, so offer the
premium or higher-quality options with prices when the user wants better than the
default.
Batches
Give generate_reaction_frames several avatars and you get one frame generation
each. Poll them together with list_generations. After the user approves the
frames they want, pass just those frameIds to generate_reaction_video. The
user can approve some and have you regenerate others, that is normal. Present
the completed frames per Delivering assets in chat, and preserve each terminal
generation's exact dashboardUrl. Never replace frame-specific links with a
generic workspace URL.
Delivering assets in chat
You are in an MCP Apps host when the product you run in renders MCP widgets
inline in the chat: claude.ai and the ChatGPT app do; CLI and editor agents
(Claude Code, Cursor, Codex) do not. There, deliver by rendering the app
view: render_image_results for frames and drafts, render_video_result
for videos (pass the videoId; reference chips resolve server-side). The
widget is the preview, the player, and the download surface.
Anywhere else, the asset's exact dashboardUrl is the delivery (the Money
and link ritual); never a generic workspace URL, and never claim an asset was
shown when it was not.
Cropping sources
Use crop_reaction_template when the user supplies or approves exact start and
end timestamps. It creates one derived template. Use
smart_crop_reaction_template when scene boundaries should be detected and
saved as several templates. Smart Crop uses FFmpeg scene-change detection; do
not describe it as semantic highlight selection or claim it understood the
performance. Both are asynchronous and free: poll their generation until
terminal, then use each successful output.id as a template id. Outputs from
Smart Crop may appear incrementally.
If an import lands needs_action, offer these tools instead of forcing a
dashboard handoff. Ask for exact timestamps when manual crop is appropriate, or
offer Smart Crop when cut boundaries are the desired split. A crop of a blocked
needs_crop source archives that unusable source after successful derivatives;
cropping a completed source preserves it and creates derivatives.
What stays in the dashboard
You do bulk creation. Everything else is a human-in-the-loop step in the dashboard: renaming or deleting templates and videos, and editing a finished clip (speed, text overlays, download). Do not try to do those over MCP.
App views (MCP Apps hosts)
When the host renders MCP Apps (claude.ai, the ChatGPT app), show
Ghostfeed content with the render tools instead of text lists or hand-built
UI: render_avatar_builder, render_source_crop, render_image_results,
render_video_result, render_inspiration_browser,
render_generation_gallery, render_slideshow_result. Never build a custom
artifact or call the
Ghostfeed HTTP API from generated code.
Widget buttons arrive as structured user messages: prepare an avatar, approve a draft, send a crop range, reuse a reference. Treat each as the user's explicit intent and follow its embedded instructions exactly. They keep this skill's gates: restate setup and cost, wait for approval before paid calls.
Workspaces
Call list_workspaces first and keep the chosen slug or id. Reads may omit
workspace and use the credential's pinned read default. Every write requires
workspace; never infer it from whichever workspace the user last opened. A typo
fails with remediation listing the valid names, slugs, and ids.
Money and link
Importing a template costs nothing. Frame generation reports an
estimatedCreditCost and charges as the frames render; video generation reports
the real creditsSpent and creditsRemaining. When a response carries
creditsSpent and creditsRemaining, state them on their own line, numbers from
the tool result:
💳 {creditsSpent} credits spent, {creditsRemaining} remainingReads and lists get no money line. get_credits has the balance if the user
asks.
When a response carries dashboardUrl, end your reply with the door on its own
line:
🔗 {dashboardUrl}