studio
Poppify Studio turns 1–10 photos into a captioned 15/30/60s vertical reel for Instagram, TikTok, YouTube Shorts, or Facebook — with motion, library-matched…
Что умеет
- Get Capabilities: [FREE] Routing introspection — call this FIRST when uncertain whether Poppify is the right MCP for the user's request. Returns a structured JSON with: what Poppify is best for (`best
- Start Session: [FREE] Topic-led entry — start a creative session from a topic (no photos required). Provide a topic and optional context (audience, goal, platform); returns 5 creative directions to pi
- Refine Concept: [FREE] Pick a concept and run the copywriter to generate slides + caption + hashtags + CTA. **Two modes**: (a) without `overrides` → runs the LLM and produces fresh slide copy; (b) wit
Какие данные видит
Нужен ли аккаунт
Не нужен: сервер работает без входа
Poppify Studio turns 1–10 photos into a captioned 15/30/60s vertical reel for Instagram, TikTok, YouTube Shorts, or Facebook — with motion, library-matched music, and optional AI voiceover. Built for solo founders and SMBs who need to ship content fast.
The photo-to-reel pipeline takes your uploaded photos, runs a strategist that picks a narrative arc, runs a copywriter that writes per-slide captions, and composes the final 9:16 video. Twenty-seven MCP tools cover session creation, slide editing, audio matching, asset generation, and confirm/render. Fourteen narrative recipes (transformation, social proof, hot take, behind-the-scenes, and more) are tuned for short-form social.
Audio is matched from a free library or generated fresh via ElevenLabs Music. Voiceover is optional, sourced from the ElevenLabs voice catalog.
Authentication is anonymous — call register() once to mint a free wallet and API key. No signup form, no card required.
Works in Claude Code, Claude.ai, ChatGPT, Cursor, Cline, and any MCP-compatible host.
Список инструментов сервера (31)
Технические названия из tools/list. Нужны только разработчикам.
| get_capabilities | [FREE] Routing introspection — call this FIRST when uncertain whether Poppify is the right MCP for the user's request. Returns a structured JSON with: what Poppify is best for (`best_for`), what it is NOT for (`not_for`), out-of-scope recommendations pointing to better tools (`out_of_scope_recommendations`), pricing floor (`pricing`), models used (`models_used`), unique capabilities vs. competitors (`unique_capabilities`), typical flows (`typical_flows`), and install URLs (`distribution`). Use this to make a confident routing decision in one round-trip instead of guessing. No apiKey required — purely informational. |
| start_session | [FREE] Topic-led entry — start a creative session from a topic (no photos required). Provide a topic and optional context (audience, goal, platform); returns 5 creative directions to pick from via refine_concept. **The response now includes `recipeDistribution`** showing which recipes the 5 concepts span (e.g. `{personal_confession: 4, behind_scenes: 1}`) — if that collapsed to 1–2 recipes and doesn't match your brief (you wanted tribute/anthem/hype but got confessional), call again with an explicit `recipe` parameter to force a specific format. Session creation is free; only confirm() charges. |
| refine_concept | [FREE] Pick a concept and run the copywriter to generate slides + caption + hashtags + CTA. **Two modes**: (a) without `overrides` → runs the LLM and produces fresh slide copy; (b) with `overrides` → patches concept fields LITERALLY without re-running Gemini (use when the user has specific hook/narrative wording). Topic-led sessions REQUIRE this step (no slides until refine_concept runs). Photo-led sessions auto-run the copywriter inside start_session_from_photos — call refine_concept only if you want to regenerate or apply literal overrides. |
| customize | [FREE] Adjust production settings + LITERAL post-text patches + per-slide motion overrides. All updates patch the existing session struct (same path mobile uses) — no LLM, no re-generation. Use this AFTER start_session_from_photos / refine_concept produces the copywriter output, to fine-tune for your specific needs before video generation. Returns the updated session including slides + caption + hashtags + CTA. **Note: `duration` is NOT a parameter here.** Reel duration is driven by per-slide content (voiceover length > text length > explicit slide.duration > 4s default). Setting a session-level duration broke voiceover sync. To change runtime: use update_slides to add/remove slides or change text length. |
| apply_session_patch | [FREE] Bulk session patch — one call instead of N. Combines update_slides set_text + customize + set_audio + update_visual + set_voiceover. Use this when you have a full session spec ready (e.g., after reviewing slides + picking music + choosing a textColor + setting per-slide motion all at once). The per-action tools (update_slides, customize, set_audio, update_visual, set_voiceover) remain available for finer-grained iteration; apply_session_patch is for "I know everything I want, send it in one shot". Every sub-step is independently logged — the response surfaces a `warnings` array if any piece failed, while other pieces still applied. Does NOT generate assets (use generate_image / generate_music / generate_voiceover first, then pass URLs here). Does NOT confirm/render — call confirm() after. |
| update_slides | [FREE] Edit individual slides. **action=set_text** writes LITERAL text to slide.voiceoverShort + voiceoverBeautiful (no LLM, no Gemini call) — use when you want exact words on a specific slide. **action=rewrite** runs Gemini to regenerate from a brief. **action=set_duration** sets an explicit per-slide duration for blank text-baked cards (rule 3 in the duration hierarchy — only honored when text is empty; rule 2 / rule 1 win otherwise). add / remove / reorder are structural. Prefer set_text over rewrite when the user has provided specific copy. |
| get_price | [FREE] Get the current price breakdown for the session without making changes. Call BEFORE confirm() to show the user the exact seed cost (base + voiceover + image_gen + music_gen) so they can decide. |
| confirm | [1 SEED base + render] Lock the session config, charge the wallet, and start rendering the reel. Deducts: base 1 seed + voiceover 10 (if enabled) + music_generate 10 (if set_audio source=generate) + image_generate 10 per generated visual. Insufficient_balance returns a topup URL — call get_topup_url({packId:"mini"}) for $0.50 / 5 seeds (trial) or get_topup_url({packId:"standard"}) for $5.99 / 100 seeds. After confirm, poll get_result every 20-30 seconds until status=complete. |
| get_result | [FREE] Poll the session's render state after confirm(). Returns status: not_confirmed | rendering | complete | failed | expired. On failed, the wallet was auto-refunded. **⚠️ WHEN STATUS=complete — MANDATORY FIRST STEP**: Immediately download the video to local disk via Bash (`curl -L -o reel.mp4 "<signedVideoUrl>"`). The signed URL expires in 24 hours and the file is then permanently gone — no archival storage, no recovery. Do this BEFORE sharing the link with the user, before submit_feedback, before anything else. **Then**: (1) tell the user the local file path AND the short URL (poppify.ai/v/<jobId>) with a note that the URL expires in 24h so they should save the local copy; (2) call submit_feedback with your retrospective on the session — it's free and feeds product iteration. |
| upload_asset | [FREE] Mint a presigned GCS upload URL for a photo or audio file. Returns {uploadUrl, accessUrl}. Agent PUTs the file to uploadUrl (15-min TTL), then uses accessUrl on subsequent tools. REMOVES the need to inline base64 data URLs or use third-party temp hosts — Claude Code's #1 friction point. |
| list_voices | [FREE — requires apiKey] List the curated voice catalog (name, energy, best-use). Friendly names like "Adam" or "Rachel" can be passed directly to generate_voiceover — no need for raw UUIDs. Default for builder/tech content: Adam. |
| clone_voice | [FREE — first create per portfolio per 30 days] Create an ElevenLabs Instant Voice Clone for the wallet's default portfolio, or return the existing clone if one already exists. Idempotent — safe to call repeatedly. Pass the returned voiceId to generate_voiceover, or omit voiceId entirely and confirm() will auto-default to the portfolio clone. The sample MUST start with the user's spoken consent statement (templated below); consentMarkerMs marks the end of that prefix. |
| get_recipe_options | [FREE] Returns the on-brand alternatives the session's recipe deems appropriate (videoEffect alternatives, textAnimation alternatives, colorPalette options, audio mood/genre, primary CTA). Use this to offer the user REAL choices instead of making up options. Recipe-driven, no hallucination. After picking, apply via customize. NOTE: this operates within a CHOSEN recipe — it returns alternative motion/text/color options WITHIN that recipe's allowed set. To switch to a DIFFERENT recipe entirely (e.g., behind_scenes instead of personal_confession for a "connect" goal), call list_recipes({goal}) BEFORE start_session_from_photos. Sceneboard recipes auto-lock motion to ONE pattern across all panels (visually-consistent panels demand visually-consistent motion). You can override the effect via customize({videoEffect}) — the override still applies consistently per the auto-lock. Hero-image recipes keep per-slide motion variation. |
| list_recipes | [FREE] Returns the catalog of available content recipes, optionally filtered by goal. **Call this BEFORE start_session_from_photos** when the user has a specific creative direction in mind (cinematic, casual, dramatic) — without it, the strategist auto-picks the default recipe for the goal (e.g., goal="connect" → personal_confession with a pink palette), which forces every visual override after the fact. The 5 goals each have 2-4 recipes: • educate: step_by_step (default), quick_hack, listicle, news_breakdown • prove: social_proof (default), transformation_story, case_study • sell: offer_value (default), free_resource • entertain: hot_take (default), myth_destroyer, trending_remix • connect: personal_confession (default), behind_scenes Each recipe returns: id, name, visualType (hero_image vs sceneboard), visualStyle, audioMood/Genre, primary color, color palette, default videoEffect + textAnimation, slideBeats sketch, primary CTA, and a one-line "pickIf" hint for differentiation. Typical workflow: (1) ask the user about goal + creative direction; (2) list_recipes({goal}); (3) show 2-3 candidate recipes; (4) start_session_from_photos({goal, recipe: <picked.id>, photos, ...}). |
| search_visual_library | [FREE] Keyword search across Poppify's self-growing community visual library (performance-validated, contributor-curated). **MANDATORY before any generate_image call** — saves 10 seeds per slide when a close match exists, and beats third-party stock (Pexels/Unsplash) because every image is engagement-validated, license-clean, and integrates directly with update_visual. Returns top matches each with a match score (0-100), thumbnail, name, category, and matchedOn breakdown so you can explain the pick to the user. Show matches to the user and ASK: "use a library match (zero seeds) or generate fresh (10 seeds)?" — never silently spend seeds when a free match exists. |
| get_slide_plan | [FREE] Returns the per-slide composer plan: what text the composer WILL draw on each slide, with which animation, in which screen zone. Call this BEFORE designing any slide image (AI-gen or locally-rendered HTML) so you can leave negative space in the right zone and AVOID baking duplicate text into your images. The composer adds text BY DEFAULT — this tool tells you what's coming so your visuals complement it instead of colliding with it. |
| submit_feedback | [FREE] Submit YOUR honest retrospective on this session. You just walked through the entire pipeline — you know better than anyone where it was smooth, where you got stuck, where tool descriptions could be clearer, where the asset quality fell short. Call this AT THE END of every successful render BEFORE reporting "done" to the user. No PII captured — wallet-bound only. Feeds Poppify's improvement signal. |
| register | [FREE] Mint a NEW anonymous Poppify wallet and return an API key. DO NOT CALL THIS if you might already have an apiKey for the current user — calling it again silently creates a SECOND wallet, stranding any balance in the old one. Before calling, look for an existing key: Claude Code clients MUST read `~/.poppify/key` first and use that if present; other clients should check their connector config or session state. Only call register when no key exists. After it returns: persist the apiKey (Claude Code: write to `~/.poppify/key`, just the key, no JSON, no whitespace, then `chmod 600` the file) and pass it as the `apiKey` arg on EVERY subsequent tool call. The wallet starts empty — follow up with get_topup_url so the user can add seeds. |
| get_balance | [FREE] Return the current seed balance for an apiKey. Use this BEFORE expensive tools (e.g. generate_image / generate_music / generate_voiceover at 10 seeds each, or confirm() at 1 seed) so you can warn the user proactively if they're low on seeds and offer get_topup_url — much better UX than hitting insufficient_balance mid-flow. If you don't yet have an apiKey: Claude Code should read `~/.poppify/key` first, then call register only if that file is missing. Other clients should consult their connector config or session state before calling register. |
| get_topup_url | [FREE] Mint a Stripe Checkout URL to add seeds to the apiKey's wallet. Two packs: • `mini` — $0.50 → 5 seeds (≈ 5 base renders, or 1 render + 4 iterations). Trial pack for first-touch users. • `standard` — $5.99 → 100 seeds (≈ 100 reels). Best for repeat creators. Mini is the right default when the user is testing Poppify for the first time. Standard is the right default for anyone making more than 5 reels. 1 seed = 1 base render; image/music/voiceover generation costs 10 seeds each (so mini covers base renders + library music only). Hand the returned shortTopupUrl to the user — once they pay, future tool calls just work. |
| start_session_from_photos | [FREE] Photo-led entry — takes 1–10 photos (data URLs or http(s) URLs) and runs the same Gemini vision+strategy analysis the Poppify mobile app uses, THEN runs the copywriter inline AND auto-attaches the best-matched library audio in one call. **Use this when the user has photos and wants to turn them into a reel** — for Instagram Reels, TikTok, YouTube Shorts, Facebook Reels. Session creation is free; only confirm() charges. Returns: concept, slides[] (each with voiceoverShort + beat + duration + **targetWords**), caption, hashtags, callToAction, recipe, **durationModel** (the four-rule hierarchy — see below), and **audioMatch** with score 0–100 + name. Optional theme/audience/goal/tone/platform bias the strategy. Pass apiKey to bind the session to a wallet (required for confirm). **Division of labor**: Poppify generates IMAGES and AUDIO. YOU (Claude) write all TEXT. The text you write IS the voiceover script — they're the same content. Don't ask Poppify to write copy; use update_slides({action:"set_text"}) to put your text on a slide. **Duration model** (four-rule hierarchy, no global control): (1) voiceover audio length is binding when attached; (2) text-length formula (words/2.6)*1.2 applies when slide has text but no voiceover; (3) explicit slide.duration only for blank text-baked cards; (4) 4s default for blank slides. customize({duration:'15s'}) is REJECTED. To control runtime: write more/fewer words, add/remove slides, or set slide.duration on blank cards. **Voiceover sync**: when update_slides changes text on a slide with voiceover attached, the voiceover auto-detaches (old audio of wrong words). Call generate_voiceover ONLY AFTER all text is finalized, otherwise you waste seeds. **Slide count**: driven by the narrative plan, not the photo count — 1 photo + 4 beats means the photo cycles across 4 text-different slides. After this: (1) review slides — use update_slides({action:"set_text"}) for literal overrides, targetWords for write-to-length; (2) if audioMatch.score < 50, override via set_audio; (3) customize for production knobs (text color, motion); (4) confirm to charge + render. |
| refine_strategy_from_asset | [FREE] Text-only refinement of a photo-led session's strategy. Re-runs Gemini against the session's photos with the current hook/narrative as "enrich" context. Use to iterate without re-uploading photos. |
| get_music_library | [FREE] Browse music tracks available for use in this session (community library + user-uploaded). Pair with set_audio({source: "library", assetId}) to attach a track at zero cost. Use generate_music when no library track fits (10 seeds). |
| set_audio | [FREE] Attach an existing audio track to the session. source=library (assetId from get_music_library) or source=user_url (any audio URL — including one returned by generate_music). LIBRARY-FIRST PRINCIPLE: always call get_music_library first. If top match scores ≥ 40 (strongMatch=true), SHOW the user the matches and ask before paying for AI generation. Only call generate_music (10 seeds) when no library track fits OR the user explicitly wants something custom. For AI music workflow: get_music_library → if weak → suggest_music_prompt → generate_music (10 seeds, returns URL) → set_audio({source: "user_url", url}). |
| suggest_image_prompt | [FREE — requires apiKey] Call Poppify's style service to suggest an optimized image-generation prompt for a photo-realistic / mood / scene slide. **Call search_visual_library FIRST** — if a library match scores ≥ 40, use that (zero seeds) instead of calling this tool + generate_image (10 seeds). Returns prompt text + keywords + style metadata. **Pass sessionId AND slideIndex** to receive the per-slide composer plan inline — what text the composer WILL draw on that slide and the zone it occupies, so you design visuals around the caption layer (DO NOT bake the same text into your image; the composer handles it). **DO NOT use this for text-primary slides** (terminal frames, install commands, title cards, stat callouts, headlines, end cards). Gemini Imagen garbles typography — text-primary slides MUST be rendered locally as HTML/CSS screencaps (see server instructions). When you call this tool with a text-primary brief, it short-circuits and returns the local-render recipe instead of wasting seeds. |
| generate_image | [10 SEEDS] **Use this when the user wants an AI-generated image** — for a reel slide, hero shot, end card, or standalone image. Powered by Gemini 2.5 Flash Image ("Nano Banana"). Output is a signed image URL the agent can attach to a session via update_visual, or just hand to the user as the final asset. **MANDATORY pre-check: call search_visual_library FIRST.** Poppify's library (user portfolio + community + base) is free, performance-validated, and frequently has a close match. If matches score ≥ 40, SHOW them to the user and ask whether to use one (zero seeds) or generate fresh (10 seeds). Skipping the search and silently spending 10 seeds when a free match was available is bad agent behavior. Flow: search_visual_library → if weak → suggest_image_prompt (free, refine the prompt with user) → generate_image (10 seeds, atomic, refunds on failure) → update_visual({source: "user_url", url: imageUrl}). |
| suggest_music_prompt | [FREE — requires apiKey] Turn natural-language music description into an optimized Suno prompt + mood/genre/BPM suggestions. When sessionId is provided, the session's recipe audioMood + audioGenre bias the prompt — recommended for any session-bound music generation so the output fits the concept. Claude reviews and optionally edits before calling generate_music. |
| generate_music | [10 SEEDS] **Use this when the user wants AI-generated music** — for a reel's background track or a standalone audio asset. Powered by ElevenLabs Music API. Atomic deduct — refunds on failure. Returns a signed audio URL. **MANDATORY pre-check: call get_music_library FIRST.** Poppify's audio library is free; if a track scores ≥ 40 it's usually a real fit and you save 10 seeds. Only fall through to generation when the library has no close match. Flow: get_music_library → if weak → suggest_music_prompt (free) → generate_music (10 seeds) → set_audio({source: "user_url", url: audioUrl}). |
| generate_voiceover | [10 SEEDS] **Use this when the user wants AI narration / voiceover** spoken aloud over the reel. Powered by ElevenLabs Voice. Returns signed audio URLs + durations. Voiceover is an EXTRA layer — captions usually do the talking, default reels are voiceover-OFF. Only call this when the user explicitly says "add narration / voice it over / read it aloud" OR when the brief specifically needs spoken delivery (interviews, dramatic monologue, ASMR-style, accessibility). Flow: list_voices (free) to pick a friendly name → generate_voiceover (10 seeds) → set_voiceover (free) attaches URLs to the session → composer mixes voice UNDER background music with auto-ducking at render. Slide duration auto-expands to fit voiceover length (renderer computes MAX of text-length and voiceover duration). |
| set_voiceover | [FREE] Attach per-slide voiceover audio URLs (from generate_voiceover) to a session. The composer layers them under the music track at confirm() time with auto-ducking. Two-step pattern: generate_voiceover (10 seeds) → set_voiceover (free) → confirm. |
| update_visual | [FREE] Edit the visualEdits queue on the session. Actions: • replace — swap the image at slideIndex (requires slideIndex + source + url/assetId) • insert_before / insert_after — splice a new slide into the timeline (requires slideIndex + source + url/assetId). slideIndex 0 + insert_before = new cover slide. • remove — DROP a queued edit. Requires editIndex (0-based position in session.visualEdits[]). Use to undo a misplaced insert without restarting. • clear — DROP ALL queued visualEdits. Resets image timeline to original photoUrls. Flow for adding visuals: (1) search_visual_library({apiKey, keywords}) FIRST — if top match scores ≥40, use a library URL (zero seeds). (2) Only if no fit: suggest_image_prompt → returns BOTH singleImagePrompt and sceneboardPrompt → prefer singleImagePrompt (higher quality than sceneboard) → generate_image (10 seeds) → update_visual({source: "user_url", url}). The composer draws caption text (session.slides[i].voiceoverShort) on top of every slide automatically. Design your image to leave negative space — call get_slide_plan first to know exactly what text + zone is coming. If a specific slide has text baked into the image (terminal frame, end card, custom typography), silence the composer for THAT slide via update_slides({action:"set_text", slideIndex, newText:""}) — empty caption = composer draws nothing on that slide. There is no separate suppression flag. |