{"tools":[{"name":"generate_media","scope":"generate:media","description":"Scope: generate:media. Generate images or videos using 40+ AI models, OR extend a completed Veo 3.1 video.\n\n============================================================\nSTOP. BEFORE you call this tool, you MUST fetch the skills\nbelow that match the request. This is not optional.\nThree equivalent fetch paths — use the FIRST one your client supports:\n  ① TOOL CALL: `get_skill(name=\"<skill>\")` — bulletproof, works in\n     every MCP client (Claude.ai web included). No fetch tool needed.\n  ② MCP `resources/read` on `kubeez://skills/<name>` (canonical)\n  ③ HTTPS GET on `https://mcp.kubeez.com/skills/<name>.md`\n     (caveat: hosted-agent web_fetch may block model-constructed\n     URLs — fall back to ① if it refuses)\nReading is free; firing this tool blind wastes user credits.\n============================================================\n  ALWAYS:\n    • kubeez://skills/model-selection — picking the right model.\n  IF the user wants a post / blog / newsletter / social / ad / thumbnail:\n    • kubeez://skills/content-creation — MANDATORY. Quality bar + propose-before-generate rule + creative-director prompting.\n  IF the user wants a MARKETING CAMPAIGN asset (paid social ad, landing hero, brand campaign, product launch, OOH):\n    • kubeez://skills/marketing-campaign — MANDATORY ALONGSIDE content-creation. Niche → visual genre cheat sheet so the asset matches the industry instead of looking like generic AI marketing slop.\n  IF the user wants a CAROUSEL / multi-slide post / LinkedIn carousel / IG carousel / 'slides about X':\n    • kubeez://skills/viral-carousel — MANDATORY ALONGSIDE content-creation. Locked design system + slide archetypes + topic-native imagery rules + asset-library cohesion workflow. Without it, multi-slide outputs drift into 10 unrelated AI images.\n  IF the user has a local file or attachment (no public HTTPS URL yet):\n    • kubeez://skills/media-upload — the 3-step upload flow.\n  IF the model is per-duration (any video, captions, separation, ref-video, audio-driven):\n    • kubeez://skills/billing-confirmation — preview cost before generating. Kubeez has NO refunds.\n  IF generation_type='extend':\n    • kubeez://skills/video-extend — Veo 3.1 extend workflow.\n  IF the user says 'Seedance' without specifying which generation/tier (2.5 vs 2 vs fast vs mini):\n    • kubeez://skills/seedance-2-tiers — disambiguation rules.\n  IF model='seedance-2-5*' AND the prompt is non-trivial (2+ reference assets, a script, keyframes, a storyboard, a blockout, >15s, or dialogue):\n    • kubeez://skills/seedance-2-5-prompting — material roles, @Image N ordering, keyframe routes, 10000-char cap.\nIf unsure whether a skill applies → fetch it. Reading is free.\n\nGENERATION TYPES:\n  • text-to-image / image-to-image / text-to-video / image-to-video — the standard set. Use get_models to confirm which a given model supports.\n  • extend — Veo 3.1 ONLY. Pass source_task_id (NOT generation_id), prompt, and extend_model. Flat-priced per tier (lite < fast < quality) — read the exact price from get_models / estimate. Skip aspect_ratio/duration/source_media_urls in this mode. See kubeez://skills/video-extend.\n\nIMAGE-TO-VIDEO vs TEXT-TO-VIDEO (READ THIS — agents pick wrong here a lot):\n  • image-to-video means a strict frame TRANSITION between exactly TWO images: image #1 = literal first frame of the output, image #2 = literal last frame, the model interpolates the in-between. Use it ONLY when the user explicitly wants a transition / morph / start-to-end shot.\n  • Animating a SINGLE image, or a video that just FEATURES one or more reference images (subject, brand, product), is text-to-video — pass the image(s) in source_media_urls and the model uses them as references. Seedance 2 text-to-video accepts up to 9 reference images; Kling 3.0 / Veo similar (see get_models).\n  • Seedance 2 specifically: image-to-video requires EXACTLY 2 images. 1 or 3+ images with image-to-video are auto-routed to text-to-video as references. If the user gave you N images and didn't say 'transition' / 'morph from A to B' / 'start frame to end frame' — use text-to-video.\n  • Seedance 2.5 DIFFERS from the 2.0 rule above: image-to-video takes 1 image (literal first frame) or 2 (first + last) — it is NOT auto-routed. 3+ images with image-to-video is a hard `too_many_images` rejection, not a silent downroute. In that mode aspect_ratio is forced to `adaptive` (output inherits the first frame's ratio) and NO other reference images/videos are accepted alongside the frames (reference audio still is). Text-to-video on 2.5 accepts up to 30 reference images / 10 videos / 10 audio. To lock a first frame AND keep extra references, use text-to-video and declare the frame in the prompt — see kubeez://skills/seedance-2-5-prompting.\n\nMODEL IDS: use only ids that came from get_models or plan — never invent or guess one. You do NOT need to call get_models every time: if you already know the id and its limits, go straight to generating. Call it when you are unsure which model fits, when you need a concrete variant, or when a capability matters (prompt_max_chars, duration_options, max_input_images, supports_negative_prompt, supports_sound, video_audio) — each row carries those plus usage_notes, and they must be respected.\n\nVARIANT DISAMBIGUATION: Some families (seedance-2, seedance-2-fast, p-video, kling-3-0-motion-control, kling-2-6-motion-control) expose multiple concrete model_ids (resolution tiers, draft vs standard, with/without video-ref). PREFER passing a concrete model_id up front: when the user gave no resolution preference, default straight to the recommended variant (e.g. `seedance-2-720p`) instead of the bare family, so there is NO round-trip. If you DO call with a bare family label, the server returns `variant_required` with a `recommended_variant`: ADOPT it and continue straight to estimate / show_plan_card, where the cost (and thus the resolution/price trade-off) is confirmed, so a separate variant question is usually unnecessary. Ask the user only when they signalled they care about price/quality, or when the applicable variants differ in CAPABILITY (not just resolution). EXCEPTION: when a reference VIDEO is attached, pass the BARE family (do NOT hand-pick a concrete id) and the server selects the correct `-video-ref` variant and its billing for you.\n\nASPECT RATIO: ALWAYS use the aspect_ratio parameter — do NOT bury 9:16 / 16:9 / 1:1 in the prompt. 'banner'/'landscape' → 16:9, 'vertical'/'story' → 9:16, 'square' → 1:1. The prompt should describe content, not dimensions.\n\nGPT-IMAGE-2 — 2K and 4K REQUIRE a non-square, non-auto aspect_ratio (9:16 / 16:9 / 4:3 / 3:4). Calling 2K/4K with auto or 1:1 returns 400 and credits are NOT held.\n\nAUDIO ON VIDEO: the `sound` parameter defaults to TRUE, so audio is ON for EVERY model that supports it unless the user explicitly asks for a silent clip (only then pass sound=false). Read capabilities.video_audio: 'included' means audio is always bundled and the sound param is ignored; 'toggle_via_sound_param' means the sound flag controls audio and DEFAULTS ON (free on Seedance 2; on Kling 2.6, Kling 3.0 and Seedance 1.5 Pro audio costs extra, so keep it on by default but state the higher audio price in your cost quote so the user is not surprised); 'silent' means no generated audio (Seedance 1.0 only). Never silence a model's audio on your own initiative. Do NOT infer audio from supports_sound alone.\n\nCONSISTENT CHARACTER + VOICE (Google Gemini Omni Video): this is the ONE family that can lock a REUSABLE on-camera identity. Every other talking-video model (Veo 3.1, Kling 3.0 Pro, P-Video) re-rolls a fresh face and voice on each generation, so a multi-clip campaign drifts. Gemini Omni Video does NOT: save a character once, then every clip renders the SAME person with the SAME voice. WHEN TO REACH FOR IT (and proactively suggest it): the user wants a recurring spokesperson / mascot / brand presenter, a video SERIES or ad set, or anything where 'keep the same person/voice across clips' matters. WORKFLOW: (1) manage_library(kind='character', action='create', descriptions=..., image_url=..., character_name=..., voice_id=...) saves the identity once (bind a voice_id from list_gemini_omni_voices so the voice is locked too); (2) for each clip call generate_media with a gemini-omni-video-* / gemini-omni-flash-* model, passing character_ids=[<character_id from list_gemini_omni_characters>] and/or voice_id=.... character_ids and voice_id are accepted ONLY by gemini-omni-video-* / gemini-omni-flash-* models (ignored elsewhere). Each character_id consumes 1 of 7 reference slots. This is VIDEO identity; it does NOT change the image-model rule (character consistency in still images stays on gpt-image-2-5-sunburst)."},{"name":"get_status","scope":"read:generations","description":"Scope: read:generations. Poll the status of ANY job — media generation, music generation, or audio separation — by id.\n\nPRE-FETCH (mandatory on the FIRST poll of a session): `resources/read kubeez://skills/polling` — first-poll delays per media type, then every 5s.\n\nAuto-dispatches: pass the id and the server figures out which kind of job it is. Optionally pass kind to skip the lookup.\n\nReturns the merged shape:\n  • kind ('media' | 'music' | 'separation') — what the id resolved to\n  • status — 'pending' | 'processing' | 'completed' | 'failed'\n  • outputs / songs / vocals_url+instrumental_url — depending on kind\n  • error_message on failure\n\nOutput URLs are CDN/R2 only — never vendor temp links. Keep polling until status='completed' AND those URLs are non-null. To SHOW the user a finished image, call view_media(output_id) — never download the CDN URL yourself."},{"name":"generate_music","scope":"generate:music","description":"PRE-FETCH (mandatory before polling the result): `resources/read kubeez://skills/polling` — first-poll delays + cadence.\n\nScope: generate:music. Not generate:media — use generate_media for image/video, this tool only for music. Generate AI music. DEFAULT to SIMPLE mode for all prompt-based requests. Only switch to ADVANCED when the user explicitly provides or asks for lyrics, a specific song title, style tags, or vocal gender. SIMPLE MODE (default): set prompt only (max 400 chars); leave title, style, vocal_gender, song_description, and lyrics empty. Optional: instrumental, model. ADVANCED MODE (custom, use only when user asks for lyrics/title/style/vocal control): set BOTH title AND style (non-empty). Required: vocal_gender = m, f, or random (random = provider picks). Content: exactly ONE of song_description OR lyrics (never both). ADVANCED VOCAL songs need explicit `lyrics` — in custom mode the provider sings the text VERBATIM, so a song_description there gets auto-routed back to SIMPLE mode (style/title folded into the prompt; response carries mode_note). If you only have a description, use SIMPLE mode directly. Advanced instrumental: song_description only (omit lyrics). Optional: negative_tags, style_weight, weirdness_constraint, audio_weight (default 0.65). Invalid: title without style or style without title. MODEL: OMIT `model` so the server auto-picks the newest, best engine (currently V6). Only set it when the user explicitly names an engine. Valid: V6 (richest detail, most natural vocals), V6_WILD (bolder, more distinctive), V6_MINI (fast and light). Every version up to V5_5 is discontinued by the provider and is silently remapped to V6 at the same price. PERSONA: to reuse a saved persona, pass persona_id (from generate_music_persona or list_music_personas). It copies the singer's VOICE by default (persona_model='voice_persona', supported across the V6 family). Pass persona_model='style_persona' to copy the genre/vibe instead. Applying a persona forces custom mode automatically. LENGTH: Suno picks its own. For an instrumental bed at a length you choose, call generate_sound(model='sonilo-text-to-music', duration_seconds=...) instead — a length passed here is served there anyway, and cannot be combined with lyrics/vocal_gender/persona_id. Returns generation_id; poll get_status(id) until completed."},{"name":"generate_music_persona","scope":"generate:music","description":"Scope: generate:music. Create a reusable PERSONA from a song you already generated, capturing its voice and style. Synchronous and FREE: returns the persona in one call (no polling). Inputs: song_id (a music_songs id from get_status/get_music_status on a completed music generation), name (<=80 chars), description (<=500 chars; the voice/mood/genre to capture), optional style tag. Requirements: the source song must be fully generated on a model above v3.5 (all current models qualify); each song can only have ONE persona (a repeat call returns the existing one). Use the returned persona_id with generate_music(persona_id=...) to sing new songs in that identity."},{"name":"list_music_personas","scope":"read:generations","description":"Scope: read:generations. List the user's saved music personas (newest first). Each entry includes persona_id, name, description, and the source song. Pass a persona_id to generate_music(persona_id=...) to reuse that voice/style."},{"name":"generate_dialogue","scope":"generate:speech","description":"PRE-FETCH (mandatory before polling the result): `resources/read kubeez://skills/polling`.\n\nScope: generate:speech. Generate text-to-speech audio. THREE providers, select with `provider`: `elevenlabs` (default, ElevenLabs v3), `google` / `gemini` (Google Gemini 3.1 Flash TTS), or `seed` (Seed 1.0 Audio by ByteDance — prompt-driven, supports image/audio references). ROUTING (set `provider='google'`): the user named Gemini/Google, OR the ask is expressive / emotional / a character / accented / non-English / intimate, OR `text` carries inline [tags]. ElevenLabs strips tags and ships flat audio, so leaving the default on those asks is the #1 mistake. Safety net: tagged text sent with the ElevenLabs default (and no explicit ElevenLabs voice) is auto-routed to Gemini, but set `provider='google'` yourself rather than relying on it. NOT music generation: do not use prompt, instrumental, or the Suno V6 model fields — use generate_music for songs. Required: `text` — the literal words and sounds to be SPOKEN, read VERBATIM. Two mistakes to avoid on EITHER provider: (1) a DESCRIPTION ('sensual vocalizations of a woman pleasuring herself' / 'soft panting') — the model just SAYS those words; (2) a bare LIST of bracketed tags ('[moaning softly], [gasping gently], [light panting]') — nothing to actually say. [bracket] performance cues are a GEMINI-ONLY feature (provider='google'): INLINE guides placed right before the words/sounds they shape, woven INTO real spoken content — e.g. text = '[about to have an orgasm] Ohhhh im going to [slight cry] cuummm, i cannot keep it anymore'. ELEVENLABS STRIPS every [tag] before synthesis (never performed), so ANY expressive / tagged / wordless-vocalization (moans, sighs, panting) ask MUST use provider='google'; on ElevenLabs `text` is just the plain spoken words (bare phonetic sounds like 'Mmm... ahh...' at most, no tags). For a Gemini wordless vocalization, write the sounds PHONETICALLY ('Mmm... ahh... ohhh') with inline cues between them ([moaning], [gasp], [breathing heavily]). Character/tone go in the `voice` choice; keep `style_prompt` minimal (default 'Say the following.', see below) — NOT a scene description. If you have only a vibe and no words/sounds to perform, ASK the user what should be said. `voice` defaults to Rachel (ElevenLabs) / Kore (Google) if omitted. Limits below are rejected with 400 and NO credit charge.\n\nELEVENLABS (provider='elevenlabs'):   • text 5-5000 characters after audio tags are stripped.   • Audio tags ([HEY], [laughs], [whispers], …) in `text`/`previous_text`/`next_text` are STRIPPED server-side — NOT performed.   • `voice`: one of 26 — Rachel, Drew, Clyde, Paul, Aria, Domi, Dave, Roger, Fin, Sarah, James, Jane, Juniper,     Arabella, Hope, Bradford, Reginald, Gaming, Austin, Kuon, Blondie, Priyanka, Alexandra, Monika, Mark, Grimblewood.   • `language_code`: one of 29 ISO codes (empirically verified — upstream rejects v2's hu/no/vi/he/fa):     ar, bg, cs, da, de, el, en, es, fi, fil, fr, hi, hr, id, it, ja, ko, ms, nl, pl, pt, ro, ru, sk, sv, ta, tr, uk, zh.     Pass `auto` to default to `en`.   • Tuning: `stability` (0-1, default 0.5), `similarity_boost` (0-1, default 0.75), `style` (0-1, default 0),     `speed` (0.7-1.2, default 1.0), `previous_text`/`next_text` (context, tags stripped).     Call `get_limits_for_model('text-to-dialogue-v3')` to confirm the current lists.\n\nGOOGLE / GEMINI (provider='google' or 'gemini'):   • text ≤4000 BYTES (UTF-8). Inline expressive tags ([sigh], [laughing], [whispering], [shouting],     [extremely fast], [long pause]) are PRESERVED and performed (unlike ElevenLabs, which strips them).   • `voice`: one of 30 — Zephyr, Puck, Charon, Kore, Fenrir, Leda, Orus, Aoede, Callirrhoe, Autonoe, Enceladus,     Iapetus, Umbriel, Algenib, Despina, Erinome, Laomedeia, Achernar, Algieba, Schedar, Gacrux, Pulcherrima,     Achird, Zubenelgenubi, Vindemiatrix, Sadachbia, Sadaltager, Sulafat, Alnilam, Rasalgethi.   • `style_prompt` (Google only): a SHORT global delivery note — overall voice character / tone for a SPOKEN-narration take     (e.g. 'Speak like a theatrical vampire host, slow and velvety'), ≤4000 bytes, default 'Say the following.'.     Do NOT describe the SCENE or the SOUNDS here (that reads like the thing to say); per-moment performance lives in the inline     [cues] woven into `text`. For wordless vocalizations, leave it at the default and let the inline cues + phonetic sounds carry it.     text + style_prompt must be ≤8000 bytes combined.   • `language_code`: a BCP-47 code (e.g. en-US, es-ES, ro-RO) or `auto` to auto-detect.     ElevenLabs tuning params (stability/similarity_boost/style/speed/previous_text/next_text) are ignored.     Call `get_limits_for_model('text-to-dialogue-gemini')` to confirm the current lists.\n\nSEED 1.0 AUDIO (provider='seed'): PRE-FETCH `resources/read kubeez://skills/seed-audio-prompting` (or get_skill('seed-audio-prompting')) BEFORE generating — it's a PROMPT-DRIVEN generative model (voices + sound effects + background music from one prose 'director's script'), and a bare line gives bare output. `text` is a free-form prompt (≤2048 chars), NOT a fixed voice reading verbatim; there is NO `voice` param and [tags]/ElevenLabs tuning do NOT apply — describe each voice (gender/age/accent/timbre/emotion) in the prose. Optional references (image and audio CANNOT be mixed): one IMAGE — `image_url` or base64 `image_data` — to guide voice/mood; OR up to 3 AUDIO references via `audio_references` (a list of {speaker | audio_url | audio_data}, each clip ≤30 s) for voice cloning. Tag each cloned voice IN THE PROMPT by upload order: <<TGT_SPK1>> / <<TGT_SPK2>> / <<TGT_SPK3>> is canonical (e.g. 'Marcus (..., the actor is <<TGT_SPK1>>) says: \"…\"'); @Voice1 / @Audio1 / @Speaker1 are also accepted and auto-normalized to <<TGT_SPKn>> server-side, so either form binds. Optional `audio_config` {format, sample_rate, speech_rate, loudness_rate, pitch_rate}. Billed by OUTPUT SECONDS (capped at 120 s) — read the per-second rate from get_models / estimate, don't assume.\n\nBilling (elevenlabs/google): PER 1000 characters (decimal ≤0.3 rounds down, >0.3 rounds up; minimum 1 credit) — read the per-1000-char rate from get_models / estimate, don't assume. Requires scope generate:speech or generate:music. Returns `generation_id`; poll `get_status(id)` until status is completed and output URLs are non-null (CDN-only). Outputs appear in `media_outputs` with `media_type=audio`."},{"name":"get_upload_url","scope":"generate:media","description":"Scope: generate:media. PRE-FETCH (mandatory): `resources/read kubeez://skills/media-upload` — explains the full flow, what the response contains, and how duration_seconds powers exact billing.\n\nCreate a temporary upload session that returns BOTH a browser link (for a human user) AND a curl-able direct upload endpoint (for AI agents with shell access — Claude Code, Cursor, etc.). REQUIRED whenever the user wants to supply a photo / video / audio file to edit, animate, reference, lip-sync, or stem-separate but does not already have a stable public HTTPS URL. Pass model_id from get_models for the generation you plan (enforces max_input_images and input_media_types). Response fields:\n  • `url` — browser link the user opens to upload (no Kubeez login).\n  • `direct_upload_url` + `direct_upload_curl_example` — POST endpoint for AI agents that already have the file on disk. Multipart form: `token` + one or MORE `file` parts (repeat -F \"file=@...\" to batch several files in one curl). Same token as the browser link; both write to the same session.\n  • `token` — pass to get_upload_session afterwards.\nIf you (the agent) already have the file locally, prefer the curl path — no need to round-trip the user. If the file is on the user's device only, share `url` and ask them to upload there. After upload(s), call get_upload_session(token) to retrieve media_urls, then pass those as source_media_urls to generate_media."},{"name":"get_upload_session","scope":"generate:media","description":"Scope: generate:media. PRE-FETCH (mandatory if you haven't already): `resources/read kubeez://skills/media-upload` AND `kubeez://skills/billing-confirmation`. The duration fields returned here are what powers exact per-second billing — handle them per the skills.\n\nFetch uploaded file URLs from a temp upload session created by get_upload_url. Call this after the user confirms they finished uploading at the link (or to poll until files appear). Pass the same token get_upload_url returned. Returns media_urls for use as source_media_urls in generate_media (or other tools expecting HTTPS media URLs). ALSO returns a per-file `media` array with {url, kind: image|video|audio, duration_seconds?, width?, height?, mime_type?, file_size_bytes?} and aggregates `total_video_seconds` / `total_audio_seconds` / `unknown_duration_count`. Use these duration fields to compute EXACT per-second costs for models like seedance-2-fast-*-video-ref (formula: (ref_video_seconds + output_seconds) × rate) and p-video with audio input (formula: audio_seconds × rate) — no need to ask the user or assume a worst case when duration is present. If `unknown_duration_count > 0`, those specific files lack probed metadata (older uploads, third-party URLs); only then should you ask the user or fall back to the pessimistic cap documented in the model's cost_note. If count is 0, nothing uploaded yet — remind them to use the link; you cannot substitute local files."},{"name":"list_assets","scope":null,"description":"Scope: generate:media. List the user's persistent Asset Library — named, reusable images / videos / audio stored in their private R2 bucket. Each entry has a stable `name` (e.g. `tesote-logo`), an `id`, `kind` (image|video|audio), `size_bytes`, and a freshly signed CDN `url` valid for 1 hour. USE WHEN: the user references a brand asset, logo, recurring face / voice / song clip, or says 'use my logo / my photo / my track' — before asking them to re-upload, check here first; their library may already have it. REFERENCING IN GENERATIONS: pass the `url` field of any matching asset directly into `generate_media` as a `source_media_urls` value — the URL is a public signed CDN link the model can fetch. Also returns `quota_bytes` / `used_bytes` so you can warn the user before calling `manage_library(kind='asset', action='add', ...)` if they're near the cap (default 50 MB). TEAM SEATS: active team members also see their team's shared assets merged into `assets` — those rows carry `shared: true`, `owner_user_id`, and `uploaded_by` {user_id, username}; a slim `team` block reports the team pool's `quota_bytes` / `used_bytes`. KLING ELEMENTS: when the user has saved any, a `kling_elements` block lists them — named reference bundles (`name`, `description`, `image_urls`, `video_urls`) they built in the app to keep a person, product or place consistent on kling-3-0-* models. Check here too before asking for a reference photo; pass any of their URLs into `generate_media` as `source_media_urls` values."},{"name":"manage_library","scope":"generate:media","description":"Scope: generate:media. Unified write surface for the user's library. Single tool that adds / renames / deletes both Asset Library entries (images, videos, audio in R2) and Gemini Omni Character entries (reference image + identity description). Reads stay on `list_assets` and `list_gemini_omni_characters` — call those first to confirm what exists. \n\nARGS:\n  `action` (required) — assets take add | rename | delete; characters take create | delete.\nAsset args (used when kind='asset'):\n  `name` — handle for 'add' (lowercase letters/digits/`_`/`-`, max 64 chars, unique per user).\n  `url` — remote source for 'add' (server fetches; validates MIME type: jpeg / png / webp / mp4 / mov / mkv / webm / mp3 / wav / m4a / ogg / weba; 50 MB total + 500 MB-per-file quota).\n  `shared` — WHERE an 'add' lands for accounts with an active team: true = the shared team library, false = the personal library. This is the USER's choice, never yours: if the account has a team (the `list_assets` response carries a `team` block), ASK the user 'personal or team library?' BEFORE saving and pass their answer. Omitting `shared` on a team account returns `shared_scope_required` (with `team_name`) instead of saving — ask, then retry. Accounts with NO team: don't ask and don't pass it; the asset is always personal.\n  `asset_id` — UUID from `list_assets` for 'rename' or 'delete'.\n  `new_name` — new handle for 'rename' (same rules as `name`).\nCharacter args (used when kind='character'):\n  `descriptions` — appearance / identity / style text for 'create'.\n  `image_url` — publicly accessible jpeg / png / webp for 'create'.\n  `character_name` — optional display name for 'create'.\n  `voice_id` — optional voice from `list_gemini_omni_voices` for 'create'.\n  `character_id` — UUID `id` field from `list_gemini_omni_characters` for 'delete' (NOT the provider's opaque `character_id` field).\n\nUSE WHEN: stashing a recurring brand asset, freeing library quota, registering a new Gemini Omni character, or cleaning up. Creating a character (kind='character', action='create') is the FIRST step for any consistent-video request: a recurring spokesperson / mascot, a video series, or a marketing ad set that must keep the SAME person + voice across clips. Bind a `voice_id` so the voice is locked too, then reuse the saved `character_id` via `character_ids` / `voice_id` on each gemini-omni-video-* / gemini-omni-flash-* `generate_media` call (see `kubeez://skills/model-selection`). For destructive actions (delete), CONFIRM with the user first if they didn't explicitly ask — both deletes are irreversible. Past generations that already used the asset URL or character_id keep their outputs; only the library row (and R2 blob, for assets) disappears.\n\nERRORS: `invalid_kind`, `invalid_action`, `missing_arg` (per action), asset-specific: `quota_exceeded`, `name_taken`, `unsupported_type`, `fetch_failed`, `shared_scope_required` (team account + no `shared` — ask the user personal vs team, retry with their pick); character-specific: `unsupported_image_type`, `invalid_voice_id`."},{"name":"list_gemini_omni_voices","scope":null,"description":"Scope: none. Return the 29-voice catalog for Gemini Omni Video. Each entry has an `id` (forward as `voice_id` to `generate_media`), `label`, `gender` (female / male / ungendered), and a one-line character description (e.g. 'Soft, high pitch'). USE WHEN: the user wants to pick a voice for a gemini-omni-video-* / gemini-omni-flash-* generation, or you want to suggest 2–3 fitting voices for the prompt before calling `generate_media`. For a CONSISTENT voice across many clips, bind the chosen `id` to a saved character (pass it as `voice_id` in `manage_library(kind='character', action='create', ...)`) so every generation that reuses the character inherits the same voice. Static list, safe to call without consuming credits."},{"name":"play_gemini_omni_voice","scope":null,"description":"Scope: none. Return a single voice's preview audio URL so the user (or the agent) can hear how a Gemini Omni voice sounds before using it in a generation. Faster than `list_gemini_omni_voices` when you already know the id (e.g. user said 'play me Kore' or you just picked one from the catalog). ARGS: `voice_id` — one of the 30 known ids (e.g. `kore`, `puck`, `achernar`). Unknown ids return `invalid_voice_id` with a hint to call `list_gemini_omni_voices`. RETURNS: `{ voice: {id, label, gender, description, preview_url}, preview_url, message }`. The `preview_url` is a ~7s mp3 hosted on the Kubeez web app — surface it directly to the user so they can play it. USE WHEN: the user asks 'what does <voice> sound like', 'play that voice', or you want to confirm a pick before passing it to `generate_media`."},{"name":"list_gemini_omni_characters","scope":"generate:media","description":"Scope: generate:media. List the user's saved Gemini Omni characters (newest first). Each character is a bundle of (reference image, description, optional voice_id) backed by an opaque provider `character_id` that the model uses to render the same identity across multiple clips. USE WHEN: the user references a saved character ('use my Maya 'character', 'render the brand mascot') — pull the matching `character_id` from this list and pass it in `character_ids` when calling `generate_media` with a gemini-omni-video-* / gemini-omni-flash-* model. ALSO check this PROACTIVELY whenever the user wants the SAME person + voice across multiple video clips (a series, a recurring spokesperson, a marketing ad set): if no fitting character exists yet, offer to create one first via `manage_library(kind='character', action='create', ...)` so the identity is locked across the whole campaign. RETURNS: `characters` (each row has `id` [internal UUID], `character_id` [the value to pass to generate_media], `character_name`, `image_url`, `description`, `voice_id`, `created_at`). Each `character_id` consumes 1 of the 7 reference slots in `generate_media`."},{"name":"trim_video","scope":"generate:media","description":"Scope: generate:media. Cut a window out of any publicly accessible video URL and host the trimmed clip — no manual upload required. The server downloads the source, runs ffmpeg `-c copy` (lossless stream-copy, no re-encode), uploads the trimmed MP4, and returns a public `url` you can pass straight into `generate_media` as a `source_media_urls` value. ARGS: `source_url` (required — public video URL); `start_s` (default 0); `end_s` (required — must be > start_s; range capped at 60s). USE WHEN: a model needs a short reference clip (Gemini Omni Video expects ≤10s; Kling Motion Control caps at 30s) and the user's source is longer than the cap. WHICH WINDOW IS THE USER'S CALL: when the source exceeds the cap, ASK the user which segment to keep (in the Kubeez chat UI, emit the trim_request block instead) and only then trim. Never silently pick a window yourself — agents defaulting to the opening seconds of a long clip discard the moment the user actually wanted. Auto-trim ONLY when the user already named the moment ('use the part at 1:20') or explicitly told you to choose. RETURNS: `{ url, size_bytes, duration_s, start_s, end_s, elapsed_s }`. ERRORS: `invalid_trim_range`, `missing_source_url`, `source_too_large` (provider cap, currently 500 MB), `trim_too_long` (>60s output), `source_fetch_failed` (source URL 4xx / unreachable), `ffmpeg_failed`, `processor_timeout`, `processor_unavailable`."},{"name":"get_models","scope":null,"description":"Scope: none (any authenticated key). List available AI models with costs, limits, and input requirements. Call it when you are unsure a model or capability exists, or when comparing models — use only model_id values returned here. NARROW THE LIST, do not read all of it: the unfiltered video catalog is over 140 rows. accepts='image'|'video'|'audio'|'document' keeps only models that take that input type. duration_s=<seconds> keeps only models that can render that clip length. sort='price' orders cheapest-first at that duration (default 'id'). verbose=false drops usage_notes / cost_note / estimated_time_seconds for a compact pick list. Example, animating a logo into an 8-second clip: get_models(model_type='video', accepts='image', duration_s=8, sort='price', verbose=false). PRICE: compare models on price_for, never on cost_per_generation. price_for maps duration in seconds to the COMPLETE credit cost at that length (e.g. {'8': 45} = 45 credits for an 8-second render), so price_for['8'] is directly comparable across every model, and its ABSENCE means the model cannot do 8 seconds. billing_unit ('per_second' | 'per_generation' | 'per_1000_chars' | 'per_minute') and rate give the underlying unit. cost_per_generation is legacy and NOT unit-comparable — for per-second models it is a pre-multiplied example duration that differs per family. Each model also returns: model_id, duration_range_s, estimated_time_seconds (REAL — DB-authored override or historical average of past completions; use it as the first-poll delay, do NOT poll every second), requires_input_media, input_media_types (e.g. ['image'] or ['image','video']), generation_types, capabilities (prompt_max_chars, duration_options, max_input_images, max_input_videos, supports_negative_prompt, supports_sound, video_audio), and usage_notes with exact rules. For video models, video_audio is critical: 'included' = audio in output without sound param; 'toggle_via_sound_param' = sound true/false controls audio and DEFAULTS ON, so audio is on for every supporting model unless you explicitly silence it (free on Seedance 2; Kling 2.6, Kling 3.0 and Seedance 1.5 Pro charge extra for audio but still default on, so state the higher price in your quote); 'always_on_flat_rate' = audio is always produced and the price does not change with it; 'silent' = no generated audio (Seedance 1.0). Only pass sound=false when the user explicitly asks for a silent clip. Do not answer 'which models have audio' using only supports_sound — that flag only means the sound parameter exists for toggling. latest_in_family maps a family to the CHEAPEST variant of its newest release. When requires_input_media is true, you MUST pass source_media_urls with the correct count and types to generate_media. If the user has media only on their device (no URL), use get_upload_url → user uploads at link → get_upload_session before generate_media — see those tool descriptions."},{"name":"pick_model","scope":null,"description":"Scope: none (any authenticated key). Resolve ONE model for a job, with priced alternates and the reason anything was excluded. PREFER THIS over reading the whole get_models catalog when you are choosing a model: the video catalog alone is 141 rows, and comparing them by eye is how a job lands on a model that costs several times what an equally capable one does. Describe the job, not a model: modality, how many images / videos / audios you will attach, duration_s, spoken_language, wants_recurring_character, nsfw, aspect_ratio. DESCRIBE THE INPUTS HONESTLY - pass images=1 when the user attached one image. A wrong count returns a model that cannot take the file, and the attachment gets ignored. Returns `primary` (the QUALITY pick for that lane), `alternates` led by the CHEAPEST capable model with its price, `rejected` naming what could not do the job and why, and `cheaper_option` when the cheap route is materially less than the primary - say BOTH numbers in your plan card when it is. tier='cheapest' inverts it and returns the cheapest capable model as primary; tier='quality' is the default behaviour stated explicitly. If the user NAMED a model, pass it as user_named_model: it is returned verbatim with its price and a capability_warning if the inputs do not fit. This tool NEVER substitutes a different family or tier for a model the user asked for. This does not generate anything and costs no credits; follow it with estimate and generate_media."},{"name":"get_skill","scope":null,"description":"Scope: none (any authenticated key). Fetch the full markdown body of a kubeez skill by name. USE THIS as the universal fallback when your MCP client cannot read MCP resources (e.g. the Claude.ai web app does NOT expose resources/read to the model) or cannot fetch arbitrary HTTPS URLs (e.g. Claude.ai web's web_fetch only allows URLs that came from the user message or a prior tool/search result, so model-constructed URLs like https://mcp.kubeez.com/skills/viral-carousel.md get blocked). Tool calls always work in every MCP client, so this path is bulletproof.\n\nAVAILABLE SKILLS:\n  • content-creation         — quality bar + propose-before-generate for posts/blogs/ads\n  • social-post              — single-image social/ad post = a DESIGNED creative (hook headline rendered on the image + caption), NOT a bare photo; 6 archetypes + prompt bar\n  • marketing-campaign       — niche-aware visual genre cheat sheet for marketing assets\n  • viral-carousel           — full design system + archetypes + prompt-construction bar for multi-slide IG/LinkedIn carousels (fetching via THIS tool also appends the user's recent deck history for anti-repeat — prefer it over resources/read for carousels)\n  • carousel-style-previews  — compact gallery of the 9 named composition systems (The Press, The Modernist, The Dossier, The Billboard, The Index, The Photograph, The Moleskine, The Whiteboard, The Screenshot) with preview URLs the user can browse\n  • model-selection          — picking the right image/video/audio model from get_models\n  • media-upload             — required when user has a local file (3-step upload flow)\n  • asset-library            — persistent named media (logos, references, songs)\n  • billing-confirmation     — preview cost before per-duration jobs (Kubeez has NO refunds)\n  • polling                  — first-poll delays per media type, then every 5s\n  • video-extend             — extending Veo 3.1 clips via generation_type='extend'\n  • seedance-2-tiers         — Seedance 2.5 vs Seedance 2 / Fast / Mini disambiguation\n  • seedance-2-5-prompting   — writing the seedance-2-5 prompt itself: material roles, @Image N mapping, keyframes, storyboards, blockouts, long-video staging, 10000-char cap\n  • critique                 — the 6-point rubric you (the calling LLM) run on your own output before shipping\n  • marketing-strategy       — hooks, platform-native copy formats, funnel/CTA matching, paid-ad angle system + character limits (the COPY twin of content-creation)\n  • product-marketing        — foundation context doc (product / audience / positioning / customer language) that copywriting, email-marketing, and launch read first\n  • copywriting              — off-image conversion copy: landing/pricing/feature pages, headlines, CTAs, value framing, 7-sweep editing pass\n  • email-marketing          — lifecycle sequences (welcome / nurture / onboarding / win-back) + B2B cold outreach and follow-ups\n  • launch                   — launch playbook: announcement sizing, owned/rented/borrowed channels, five phases, checklist\n\nReturns: { name, uri, url, content_type, content } where `content` is the full skill markdown body. Same source-of-truth as the kubeez://skills/<name> MCP resource and the https://mcp.kubeez.com/skills/<name>.md HTTPS endpoint — pick whichever path your runtime supports. CALL THIS BEFORE generate_media / generate_music / generate_dialogue when the matching skill applies; firing those tools without the relevant skill loaded leads to AI-slop output and wasted credits."},{"name":"log_carousel_deck","scope":"generate:media","description":"Scope: generate:media. Record the locked design system of a carousel deck — composition_system (required, e.g. 'The Modernist'), palette, mark_style, card_treatment, recipe, topic — the moment the user approves it, BEFORE generating slide 1. This powers the 'Recent decks by THIS user' anti-repeat section that get_skill('viral-carousel') injects: without the log, every deck starts from zero history and drifts back to the same look. Free — no credits charged. Returns { logged, recent_decks } so you can sanity-check variety across the user's last decks."},{"name":"get_balance","scope":"read:balance","description":"Scope: read:balance. Check the user's current credit balance. Always call this before generate_media or generate_music to avoid 'insufficient credits' errors. Returns available credits; each generation consumes credits based on the model (see get_models for cost_per_generation)."},{"name":"get_generations","scope":"read:generations","description":"Scope: read:generations. Return the user's media-generation history, newest first — covers BOTH the current session's jobs (still queued / processing / just-completed) AND prior sessions. Each row includes id, prompt, model, generation_type, status, created_at, completed_at, processing_time_ms, credits_deducted, error_message, and an `outputs[]` array with the playable/viewable URL(s), thumbnails, dimensions, duration, format, and file size — enough to render a gallery without a follow-up call. `limit` is 1–25 (default 25; higher values are clamped). Optional filters: `status`, `model` (model_id), `generation_type` (text-to-image, image-to-video, etc.). Use this when the user asks 'what have I made', 'show me my last 10 videos', or to surface re-usable assets. For a single generation's full live status (poll until done), use get_status(id)."},{"name":"generate_captions","scope":"generate:media","description":"PRE-FETCH (mandatory): `resources/read kubeez://skills/media-upload` AND `kubeez://skills/billing-confirmation`. Captions are billed per-second — preview cost first.\n\nCaption a video end to end: transcribes the speech, then burns word-timed captions onto the video server-side and returns a finished MP4. Captions are styled automatically and placed to avoid faces, on-screen text, and the main subject. Scope: generate:media. USE 'auto-caption' AS THE MODEL ID EVERYWHERE — upload and estimate both take it, and estimate returns the COMPLETE price in one call. Internally the job bills two per-second lines (speech-to-text, then the burn-in render) and estimate already sums both, itemized under `breakdown`; do NOT add a second estimate on top or you will double-quote. The render fee is credit-checked BEFORE transcription runs, so an unaffordable job never bills a partial step. Max 30 minutes per video. CANONICAL FLOW: (1) if the user has a local file, call get_upload_url(model_id='auto-caption'). (2) after upload, get_upload_session(token) returns `total_video_seconds`. (3) estimate(model='auto-caption', reference_video_seconds=<total_video_seconds>) — this is the full price. (4) quote it, ask for go-ahead. (5) only then call this tool. Required: media_url — a public HTTPS URL of the uploaded video. Optional: quality — 'auto' (default, auto-detects language, cheaper) or 'best' (specify a known language for higher accuracy). When quality='best', pass language as one of en/es/fr/de/it/pt. Optional: code_switching — true if the video mixes multiple spoken languages in one track. Optional manual placement (defaults to automatic content-aware placement): caption_x / caption_y — percent offsets of the caption block's center from the video center (x negative = left, y positive = down; y=18 is the standard lower zone); caption_scale — absolute caption size multiplier. Pass these ONLY when the user positioned the captions themselves. Async: returns {generation_id, status: 'processing', credits_charged} immediately. Poll get_generation(generation_id) until the captioned video appears in outputs."},{"name":"generate_separation","scope":"generate:media","description":"PRE-FETCH (mandatory): `resources/read kubeez://skills/media-upload` AND `kubeez://skills/billing-confirmation`. Separation is billed per-minute of audio — preview cost first.\n\nSplit an audio track into vocals + instrumental stems (MVSEP BS Roformer). Scope: generate:media. Billing: per-minute of audio duration; auto-resolved from media_upload_metadata. CANONICAL FLOW: (1) if the user has a local file, call get_upload_url(model_id='mvsep-40') and send the link. (2) when they confirm upload, call get_upload_session(token) — it returns `total_audio_seconds`. (3) call estimate(model='mvsep-40', reference_audio_seconds=<total_audio_seconds>) to get the exact price. (4) tell the user the price and ask for confirmation. (5) ONLY THEN call this tool. Don't charge silently. Required: media_url — a public HTTPS URL of the uploaded audio. Async: returns {separation_id, task_id, status: 'pending'} immediately. Poll get_status(separation_id) until status is 'completed'. When completed, the response carries vocals_url + instrumental_url (permanent CDN). For a full workflow with preview + library, direct the user to kubeez.com/audio/separation."},{"name":"generate_sound","scope":"generate:media","description":"PRE-FETCH (mandatory for the video models): `resources/read kubeez://skills/media-upload` AND `kubeez://skills/billing-confirmation`. Every model here bills PER SECOND and Kubeez has no refunds — quote with `estimate` before you fire.\n\nScope: generate:media. Sound effects and music, either from a sentence or written against a clip you already have. NOT for songs with vocals — that is generate_music (Suno).\n\nPICK BY WHAT YOU HAVE:\n  You have a sentence:\n    sonilo-text-to-sfx    one described sound effect. 1-180s, default 5s. 3s billing floor.\n    sonilo-text-to-music  an instrumental track. 5-360s, default 60s. 10s billing floor.\n  You have a clip (video_url REQUIRED, https, up to 360s):\n    sonilo-video-to-sfx           effects matched to the picture, as a bare audio track\n    sonilo-video-to-music         a score written to the cut, as a bare audio track\n    sonilo-video-to-sound         both, mixed, as a bare audio track\n    sonilo-video-to-video-sfx     same as above but returns the CLIP with the sound in it\n    sonilo-video-to-video-music   same, muxed\n    sonilo-video-to-video-sound   same, muxed\n  Prefer a `-video-to-video-*` id when the user wants a finished clip back; prefer the bare audio ids when they will mix it themselves in an editor.\n\nPROMPTS: pass `prompt` and it is folded into the field that model reads. The two `*-sound` models write effects AND music, so they take `music_prompt` and `sfx_prompt` separately and reject a lone `prompt`. Max 2000 chars each. A video run with no prompt at all is valid — the model writes to what it sees.\n\nBILLING: per second of the CLIP, not of the output, and each product carries the provider's OWN minimum billable length: 10s on anything that writes MUSIC (both mixed `*-sound` ids included), 3s on the effects-only ids. A 6s score bills 10s; a 2s effect bills 3s. estimate(model=..., reference_video_seconds=<clip seconds>) applies it.\n\nCLIP LENGTH: a video run is priced from the clip, so its length must be known. It is resolved for you when the URL came from get_upload_url (probed at upload) or from one of your own Kubeez generations; otherwise pass duration_seconds. Without it the run is refused here rather than failing at the provider.\n\nLOCAL FILE: get_upload_url(model_id='sonilo-video-to-video-sfx') → upload → get_upload_session(token) → pass the returned URL as video_url. Uploading this way also probes the duration, which is what makes the price exact instead of pessimistic.\n\nASYNC: returns {generation_id, status:'pending'}. Poll get_status(generation_id). Sonilo has NO provider callback, so the poll is also what settles the run — keep polling until the status is completed or failed, or the credits stay reserved."},{"name":"generate_dubbing","scope":"generate:media","description":"PRE-FETCH (mandatory): `resources/read kubeez://skills/media-upload` AND `kubeez://skills/billing-confirmation`. This is the most expensive model on the platform and Kubeez has no refunds — quote with `estimate` and get the user's explicit go-ahead before you fire.\n\nScope: generate:media. Translates and re-voices the speech ALREADY IN a clip, and returns one dubbed .mp4 per language. It does not write music, effects or subtitles, and it does not add speech to a silent clip.\n\nINPUT: video_url, https, up to 300 seconds (the provider's own ceiling, lower than the 360 the scoring models take). Local file: get_upload_url(model_id='sonilo-dubbing') → upload → get_upload_session(token) → pass the returned URL. Uploading that way probes the duration, which is what makes the price exact.\n\nLANGUAGES (required, no default): en, zh_cn, ja, ko, pt, pt_br, es, es_419, de, fr, it, ru, th, ar, tr, vi, id. `pt_br` is Brazilian Portuguese and `es_419` Latin American Spanish; plain `pt` and `es` are the unqualified languages, so they are a third option rather than synonyms. `ar` is unqualified Arabic. Romanian is NOT available. ASK the user which languages they want — do not pick a set for them, because each one is charged in full.\n\nBILLING: clip seconds MULTIPLIED BY the number of languages, with a 10-second minimum applied to that product (not per language). Three languages costs three times one. estimate(model='sonilo-dubbing', reference_video_seconds=<clip seconds>, variant_count=<how many languages>) is the quote.\n\nDUCKING: optional, free, off by default. On, the clip's own music and effects are lowered while the dubbed voice speaks; the bed is kept either way.\n\nASYNC: returns {generation_id, status:'pending', languages}. Poll get_status(generation_id) — the provider has NO callback, so the poll is also what settles the run and releases any language that was not delivered. Each output's metadata.language names which language it is."},{"name":"remove_background","scope":"generate:media","description":"PRE-FETCH (mandatory): `resources/read kubeez://skills/media-upload` AND `kubeez://skills/billing-confirmation`.\n\nCut the subject out of an image. The output is a PNG with a REAL transparent background (alpha) — use this instead of prompting an image model for a 'transparent background', which bakes a white or checkerboard fill into the pixels. Ideal for logos, product shots, mascots, and cutouts you will composite over something else.\n\nScope: generate:media. Billing: flat per image — call estimate(model='remove-background') for the exact price, tell the user, and get confirmation before calling this. Takes NO prompt: it is an operation on an existing image, not a generate_media model.\n\nCANONICAL FLOW: (1) if the user has a local file, call get_upload_url(model_id='remove-background') and send them the link. (2) when they confirm the upload, call get_upload_session(token) to get the public URL. (3) quote the price and ask for confirmation. (4) ONLY THEN call this tool.\n\nRequired: image_url — a public HTTPS URL of a .jpg / .jpeg / .png / .webp image (max 5MB; the provider rejects other formats). An already-generated Kubeez image works directly — pass its output URL.\n\nAsync: returns {generation_id, status: 'pending'} immediately. Poll get_status(generation_id) until status is 'completed'; the output URL is the cutout PNG. Also available in-app at kubeez.com (image tools → Background Remover)."},{"name":"estimate","scope":null,"description":"Scope: none (any authenticated key). Preview BOTH the credit cost AND the processing time of a generation WITHOUT deducting credits.\n\nPRE-FETCH (mandatory): `resources/read kubeez://skills/billing-confirmation` — Kubeez has NO refunds. The skill explains when a preview is mandatory vs optional and how to phrase the confirmation.\n\nPass the same model_id you'd pass to generate_media / generate_music.\n\nPer-duration video models — pass duration (seconds). Audio defaults to ON across all video models (matches generate_media's default). For Kling 3.0 this means the per-second audio surcharge (rate in get_models — don't assume) is reflected by default; pass sound=false to preview the silent rate. For Seedance 2 audio is free (same rate either way). Seedance 2 with video reference — pass reference_video_seconds (from get_upload_session.media[].duration_seconds; if you don't have it, omit and the server bills the pessimistic 15s cap). P-Video with audio input — pass reference_audio_seconds.\n\nReturns:\n  • credits: { estimated_credits, mode, rate_per_second?, explanation, pessimistic }\n  • time:    { estimated_seconds, source }\n  • price_for: seconds → the COMPLETE credit cost at that length, for THIS model, when it supports more than one. Offer the user the other lengths instead of silently keeping yours — duration is the biggest lever on a per-second bill. In agent-chat, pass these straight to show_plan_card as duration_options and the card renders tappable length chips.\nWhen credits.pessimistic is true the real billed amount may be lower — suggest the user re-upload via get_upload_url so duration is probed, then re-estimate."},{"name":"plan","scope":null,"description":"Scope: none (any authenticated key). Read-only planner. Input: free-text user `intent` (REQUIRED — e.g. 'a hero image for my coffee brand', 'animate my product photo', 'add a cigar to the monkey in this video', 'separate vocals from this song'). Optional flags — PASS THESE when relevant or the planner will mis-route: `has_local_files=true` if the user attached image / audio reference media; `has_video_reference=true` if the user attached a VIDEO and wants it modified (video-to-video editing — e.g. \"add X to this clip\", \"restyle this video\", \"make the subject do Y\"). When video_reference is true the planner routes to a video-ref model (Gemini Omni Video HD video-ref) instead of text/image-to-video, which is the correct choice for editing an existing clip. Output: a structured plan with `recommended_model`, `estimated_credits`, `estimated_seconds`, `requires_upload`, `suggested_aspect_ratio`, `workflow_steps[]`, and `skills_to_fetch[]`. Use this as the FIRST call of a session when the user describes a goal but hasn't picked a model. It collapses get_models → get_balance → estimate → human-guidance into a single round-trip and tells you exactly which skills to read before generating. After the plan, fetch each item in `skills_to_fetch` (via `get_skill` or `resources/read kubeez://skills/<name>`), then follow `workflow_steps` in order. This tool does NOT charge credits and does NOT call any model — it's pure deterministic routing on top of the model catalog + skill index. If the intent is ambiguous, the plan returns `clarifying_questions[]` instead of a model recommendation; ask the user those, then call plan again."},{"name":"get_generation","scope":"read:generations","description":"Scope: read:generations. Fetch a SINGLE past generation by id — full detail (prompt, model, params, status, outputs, credits_deducted, error_message). Use when you have a specific id and want one row; vs `get_generations` which returns a paginated history. Pairs with `regenerate(id, ...)` for iteration: get_generation(id) → inspect params → regenerate(id, prompt='tweaked'). Returns the same row shape as one entry of `get_generations.generations[]`."},{"name":"view_media","scope":"read:generations","description":"Scope: read:generations. Return one of YOUR generated outputs as inline media, so it is displayed rather than linked. Pass `output_id` — the `id` inside outputs[] from get_status / get_generation / get_generations (the OUTPUT id, not the generation id). USE THIS when you want the user to actually SEE an image you just made, and ALWAYS instead of downloading media.kubeez.com yourself: many hosts block that origin in their sandbox, and fetching it externally wastes a turn and a second copy of the file. Images and audio come back as inline media. Video, anything over the 3.5 MiB inline limit, and any CDN failure come back as a short text pointer to the URL instead — MCP has no inline video block. Owner-only: another user's output is refused."},{"name":"regenerate","scope":"generate:media","description":"Scope: generate:media. Re-run a past media generation with optional overrides. Use after `critique` flags an issue: `regenerate(id, prompt='same concept but fix the warped hand')`. Original row's prompt / model / aspect_ratio / source_media_urls / duration are reused unless you override them. Overrides: `prompt` (replaces original), `seed` (force a new seed; 0 = let provider pick), `aspect_ratio`, `negative_prompt`, `variant_count` (1-4 — fans out N parallel calls and returns N generation_ids). Only works on `media_generations` rows owned by the caller. Music / separation / dialogue regeneration is not supported via this tool — call the original tool again. Cost is identical to a fresh generate_media — confirm with the user before calling, especially for per-duration video models."},{"name":"open_studio","scope":null,"description":"Scope: none. Return a deep-link the user can open to land in the Kubeez studio with their context prefilled. Use when the agent has done the planning + first generation and the user wants to continue iterating in the UI (richer controls, side-by-side compare, drag-and-drop refs). Optional: `prompt`, `model`, `aspect_ratio` — get appended as query params. Returns `{ url, summary }`. Just hand the url to the user; do NOT try to render the page."},{"name":"open_gallery","scope":null,"description":"Scope: none. Deep-link to the user's generations history page. Use when the user asks 'show me my last X' or wants to browse / share past generations visually rather than via `get_generations` JSON. Optional `status` filter is appended as a query param. Returns `{ url, summary }`. On hosts supporting MCP Apps this also renders the thumbnails inline."},{"name":"open_pricing","scope":null,"description":"Scope: none. Deep-link to the Kubeez pricing / credit top-up page, with the user's CURRENT balance surfaced in the summary so the agent can phrase the suggestion without an extra `get_balance` call. Use when a generation fails with `insufficient_credits`, when `estimate` shows a cost > balance, or when the user explicitly asks to buy more. Returns `{ url, summary, current_balance }`. Phrase to the user e.g. 'You have N credits, this run needs M — top up at <url>'."}],"total_count":33}