Niche — Editorial Intelligence
このMCPでできること
Discovers and ranks emerging stories, proposes editorial angles, generates platform-specific content, renders media, and supports publishing workflows.
ツール
入力スキーマ
{'type': 'object', 'required': ['session_id'], 'properties': {'cell': {'type': 'string', 'description': "Cell to add. Must be one of the valid cells (see niche_signal_scan's target_outputs for the list)."}, 'session_id': {'type': 'string'}, 'remove_cell': {'type': 'string', 'description': "Cell to remove from this run (deletes the produced output). Idempotent: a no-op message when the cell isn't present. Pass this instead of `cell` to remove rather than add."}, 'estimate_only': {'type': 'boolean', 'default': False, 'description': "When true, return the credit cost of adding this cell without creating a row or generating. Returns 0 when the cell's platform family already generated (text is reused)."}}}
入力スキーマ
{'type': 'object', 'required': ['session_id', 'story_id'], 'properties': {'lens': {'type': 'string', 'description': "Optional steer (e.g. 'more contrarian', 'lead with the data'). Read on a regenerate run, and as a fallback steer for a custom_framing draft when custom_framing is set without its own wording."}, 'story_id': {'type': 'string', 'description': 'id from niche_session_state.stories[].id'}, 'regenerate': {'type': 'boolean', 'description': 'Generate a fresh set of angles on the locked story instead of returning the existing set. Requires the story locked and angles ready. Capped per session; metered like a generation.'}, 'session_id': {'type': 'string'}, 'custom_framing': {'type': 'string', 'description': "Optional. Your own angle in a sentence ('the contrarian take: X is overhyped because Y'). When set, drafts that angle on the real story (provenance preserved) instead of returning the proposed five. Requires the story locked and angles ready (cp2_awaiting_angle)."}}}
入力スキーマ
{'type': 'object', 'required': ['session_id', 'cell'], 'properties': {'cell': {'type': 'string', 'description': "Post cell to attach onto (e.g. 'linkedin:image_post', 'x:image_post', 'instagram:image_post')."}, 'image': {'type': 'object', 'required': ['mime_type', 'data_base64'], 'properties': {'name': {'type': 'string'}, 'mime_type': {'type': 'string'}, 'data_base64': {'type': 'string'}}, 'description': 'Inline image as base64: {mime_type, data_base64} (plus optional name). Max 8MB, but large inline payloads are unreliable over MCP, so prefer upload_ref or image_url. One of upload_ref / image_url / image / image_chunk is required.'}, 'image_url': {'type': 'string', 'description': "A fetchable image URL (https). For an asset that already lives on the web: the server fetches and stores it, so you don't inline a large base64 payload. One of upload_ref / image_url / image / image_chunk is required."}, 'session_id': {'type': 'string'}, 'upload_ref': {'type': 'string', 'description': "FAST bring-your-own path. First POST the raw file to /asset/upload (multipart/form-data field 'file', Authorization: Bearer <token>); the response returns an upload_ref. Pass it here. The bytes go over HTTP, not through the model, so it's instant for a real graphic. Preferred whenever the agent can run a shell."}, 'image_chunk': {'type': 'object', 'required': ['data_base64'], 'properties': {'seq': {'type': 'integer', 'description': '0-based order of this chunk.'}, 'name': {'type': 'string'}, 'final': {'type': 'boolean', 'description': 'true on the last (or only) chunk, triggers assemble + attach.'}, 'sha256': {'type': 'string', 'description': "hex sha256 of THIS chunk's raw bytes (before base64). Strongly recommended: the server verifies it and tells you to resend the chunk if it was mis-transcribed."}, 'mime_type': {'type': 'string', 'description': 'image/png | image/jpeg | image/webp (validated on the final chunk).'}, 'upload_id': {'type': 'string', 'description': "Returned by the first chunk's response; pass it on every subsequent chunk."}, 'data_base64': {'type': 'string', 'description': "base64 of THIS chunk's raw bytes."}, 'total_sha256': {'type': 'string', 'description': 'On the final chunk only: hex sha256 of the WHOLE assembled file, for an end-to-end integrity check.'}}, 'description': "Chunked upload for bring-your-own bytes you hold locally (no URL). Split the file's BYTES into ~32-48KB pieces, base64-encode EACH piece, send in order. Always include a per-chunk `sha256` (hex of that piece's raw bytes): the server verifies it and rejects a mis-transcribed chunk so you resend just that one, which is what makes this path reliable (valid base64 can still decode to wrong bytes and silently corrupt the image). Omit upload_id on the first chunk (the response returns one); set final:true on the last, optionally with `total_sha256` of the whole file."}}}
入力スキーマ
{'type': 'object', 'properties': {'brand_id': {'type': 'string', 'description': 'Optional. If set, also reads the persisted BrandProfile for that brand_id so questions whose answers live in the profile (banned_terms, framing.allowed, etc.) get their is_already_set computed against profile state too.'}, 'include_filled': {'type': 'boolean', 'default': False, 'description': 'If true, return all questions including those already answered. Default false: the agent only sees the gaps.'}}}
入力スキーマ
{'type': 'object', 'properties': {'text': {'type': 'string', 'description': 'Optional URL (homepage, Substack, portfolio, LinkedIn) or a paste of brand text (tagline, boilerplate, voice notes). URL takes precedence when both look URL-shaped.'}, 'files': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'mime_type', 'data_base64'], 'properties': {'name': {'type': 'string'}, 'mime_type': {'type': 'string'}, 'data_base64': {'type': 'string'}}}, 'description': 'Files to ingest (logos, brand-guide PDFs, screenshots, color swatches, headshots). Each entry: {name, mime_type, data_base64}. The engine classifies each by image content and routes to logo/wordmark/headshot/brand-guide/color-swatch slots.'}, 'replace': {'type': 'boolean', 'description': "Default false (additive: fill empty fields only). Set true only after the user confirms they want an already-populated brand re-learned from this source; it overwrites the detected identity fields. Do not set true to silently clobber a different brand's kit; create a new brand_id instead."}, 'brand_id': {'type': 'string', 'description': "Which brand slot to ingest into. Omit for the default brand. Pass a slug (e.g. 'acme') to target or create a separate brand kit; do this when the default kit already belongs to another brand, so you don't merge two brands into one. If the account isn't entitled to additional brands, the call returns a clear error explaining what's needed."}, 'brand_name': {'type': 'string', 'description': "Optional display name when creating a new brand_id slot (e.g. 'Acme Co')."}}}
入力スキーマ
{'type': 'object', 'required': ['ingest_id'], 'properties': {'ingest_id': {'type': 'string', 'description': 'The ingest_id returned by niche_brand_kit_ingest.'}}}
入力スキーマ
{'type': 'object', 'properties': {'name': {'type': 'string', 'description': "Rename an existing brand's display name (its brand_id is unchanged)."}, 'accent': {'type': 'string'}, 'archive': {'type': 'boolean', 'description': 'true archives the brand (soft, reversible; hidden from every list, not deleted); false restores it. Refuses the default/active brand and refuses a brand with published history unless acknowledge=true.'}, 'tagline': {'type': 'string', 'description': 'Brand tagline (5-12 words).'}, 'brand_id': {'type': 'string', 'description': 'Which brand slot to update. Omit for default; pass a slug to target a specific brand kit (for multi-brand accounts).'}, 'cta_text': {'type': 'string', 'description': "One-line call to action appended to generated posts, e.g. 'DM to commission'."}, 'logo_url': {'type': 'string'}, 'brand_name': {'type': 'string', 'description': 'Display name when creating a new brand_id slot.'}, 'primary_bg': {'type': 'string', 'description': 'Primary background color (#rrggbb).'}, 'acknowledge': {'type': 'boolean', 'description': 'Required (true) to archive a brand that has published history, confirming the user means to remove it from the roster even though it shipped work.'}, 'boilerplate': {'type': 'string', 'description': 'Brand boilerplate (one paragraph, ≤350 chars).'}, 'voice_notes': {'type': ['string', 'array'], 'items': {'type': 'string'}, 'description': 'Voice direction in plain prose. A string or a list of strings (the guided-setup text_list form); a list is joined server-side.'}, 'external_url': {'type': 'string', 'description': 'Canonical brand URL (homepage).'}, 'headshot_url': {'type': 'string'}, 'secondary_bg': {'type': 'string'}, 'text_primary': {'type': 'string'}, 'wordmark_url': {'type': 'string'}, 'archive_scope': {'enum': ['test'], 'type': 'string', 'description': "With archive=true, 'test' archives every scratch brand in one call (never the default). Returns the list of brands archived; each is reversible with archive=false."}, 'text_secondary': {'type': 'string'}, 'body_font_family': {'type': 'string'}, 'endcard_template': {'enum': ['classic', 'bold_center'], 'type': 'string'}, 'forbidden_phrases': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Phrases never to use in generated copy. Max 16; trimmed beyond.'}, 'signature_phrases': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Phrases the brand uses. Max 16.'}, 'endcard_outro_text': {'type': 'string'}, 'headline_font_family': {'type': 'string'}, 'video_voice_preference': {'enum': ['auto', 'male', 'female'], 'type': 'string'}, 'endcard_mark_preference': {'enum': ['auto', 'logo', 'wordmark', 'headshot', 'text_only'], 'type': 'string'}}}
入力スキーマ
{'type': 'object', 'properties': {'brand_id': {'type': 'string', 'description': 'Brand identifier to read. Omit to list all.'}}}
入力スキーマ
{'type': 'object', 'required': ['brand_id', 'profile'], 'properties': {'profile': {'type': 'object', 'description': "The brand profile JSON. Schema sections (each drives a pipeline stage): identity (who the brand is), audience (who it's for), voice (register/rhythm/lexicon rules the copy obeys), lexicon (canonical_terms plus banned_terms the verifier enforces), framing (frame slugs from Niche's editorial taxonomy; pass `allowed` and/or `blocked` as lists of slugs. The set is closed: an unrecognized frame rejects the set and the error returns the valid slugs, so nothing is silently dropped), structure (default article shapes), offers (what the creator sells: {product, what_it_is, proof_point?, cta, link}; lands signal-led posts on the offer), verifier_overrides (truthfulness thresholds), channels (per-platform config), source_quality (trusted/blocked domains), compliance (required disclosures), metadata. Fill the load-bearing ones (identity / voice / lexicon / framing / verifier_overrides) for a strong profile; the rest are optional. A malformed set returns a `template` of the full schema with inline field docs in the error response, so no separate discovery call is needed."}, 'brand_id': {'type': 'string', 'maxLength': 64, 'minLength': 1, 'description': "Unique-per-user identifier for this brand (e.g. 'acme', 'acme-blog'). Stored as a string; lowercase kebab-case recommended."}, 'schema_version': {'type': 'string', 'default': '1.0', 'description': "Profile schema version. Defaults to '1.0'."}}}
入力スキーマ
{'type': 'object', 'required': ['session_id', 'angle_id'], 'properties': {'angle_id': {'type': 'string', 'description': 'id from niche_angle_propose.angles[].id'}, 'brand_id': {'type': 'string', 'description': "Bind this draft to a brand (its voice/offer/CTA). Usually unnecessary (the run inherits the scan's brand). Pass it to answer a `brand_choice_required` on a multi-brand account, or `'none'` to draft deliberately unbranded."}, 'session_id': {'type': 'string'}, 'estimate_only': {'type': 'boolean', 'default': False, 'description': 'When true, return the per-platform content credit cost without locking the angle or generating. Quote a price before committing.'}}}
入力スキーマ
{'type': 'object', 'properties': {'take': {'type': 'string', 'description': "What the post should say: the creator's own message or product. Required unless a source is given."}, 'image': {'type': 'string', 'description': "Produce an image in the same run when the user wants one: 'photo' = generated AI image (paid, ~30 credits/cell), 'card' = free brand card. Omit for text-only."}, 'verify': {'type': 'boolean', 'description': 'When true, run the per-claim grounding check against the provided source and record the clean result on each output, so the draft reads checked-and-clean rather than unchecked. Recommended when repurposing a source_url or source_text.'}, 'brand_id': {'type': 'string', 'description': "Binds this brand's voice, colors, offer, and CTA to the piece. Omit to use your default brand; on a multi-brand account pass the slug explicitly so a post about one product is not bound to another brand's identity. niche_whoami lists your brands."}, 'source_url': {'type': 'string', 'description': "Optional page to repurpose: fetches and extracts the page's readable text. Use to turn an essay or landing page into platform posts."}, 'source_text': {'type': 'string', 'description': 'Optional pasted source text to repurpose (alternative to source_url).'}, 'source_title': {'type': 'string', 'description': 'Optional title for the pasted/fetched source.'}, 'target_outputs': {'type': 'array', 'items': {'type': 'string'}, 'description': "Cells to draft, e.g. ['linkedin:text_post','x:thread','instagram:image_post']. Bare platforms (linkedin/x/instagram) coerce automatically. Defaults to ['linkedin']."}}}
入力スキーマ
{'type': 'object', 'required': ['session_id', 'platform'], 'properties': {'dry_run': {'type': 'boolean', 'default': True}, 'platform': {'type': 'string', 'description': 'Accepts either:\n • cell string: the platform×content_type the run produced (linkedin:text_post, linkedin:image_post, linkedin:carousel, x:single_tweet, x:thread, x:image_post, instagram:image_post, instagram:carousel), preferred for new code\n • family name: linkedin, linkedin_carousel, twitter, instagram, which coerces to the registered default cell for that family (e.g. linkedin maps to linkedin:text_post)\nReels (linkedin:reel / x:reel / instagram:reel) and long_form_article have no in-app publish path: reels get a download URL, long-form ships as Markdown for paste-to-Substack.'}, 'session_id': {'type': 'string'}, 'scheduled_for': {'type': 'string', 'description': "ISO-8601 datetime to publish this output later (must be in the future). Files a pending scheduled post and returns status='scheduled' instead of publishing now. Requires the platform's social account connected; long-form has no scheduled path."}, 'idempotency_key': {'type': 'string', 'description': 'Required when dry_run=false. Same key returns the prior result without re-publishing.'}, 'cancel_scheduled': {'type': 'boolean', 'default': False, 'description': 'Clear a pending scheduled post for this platform on this run. Idempotent: returns a clean message when nothing is scheduled.'}, 'acknowledge_surfaced': {'type': 'boolean', 'default': False, 'description': "Required true to commit when the dry-run surfaced claims/flags worth a human look (the dry-run's next_step names them). Affirms the agent showed the human the surfaced items and got go-ahead. Ignored when nothing was surfaced. Default false."}, 'acknowledge_publish_cost': {'type': 'boolean', 'default': False, 'description': 'Required true to commit an X post that carries a link (X charges per link-post, so the dry-run returns a `publish_cost` in credits). Affirms the agent showed the human the cost and got go-ahead. Ignored when the publish is free (plain X / LinkedIn / Instagram). Default false.'}}}
入力スキーマ
{'type': 'object', 'required': ['output_id'], 'properties': {'caption': {'type': 'string', 'description': 'Replace the full caption (legacy field; same effect as setting `script.body` for LinkedIn, `script.caption` for Instagram).'}, 'hashtags': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Replace the hashtag list. Sanitized server-side (whitespace stripped, non-alphanumerics removed, case-insensitive dedup).'}, 'output_id': {'type': 'string'}, 'slide_patches': {'type': 'array', 'items': {'type': 'object', 'required': ['index'], 'properties': {'op': {'enum': ['move', 'insert', 'delete'], 'type': 'string', 'description': 'Structural op. Omit for an in-place headline/body edit.'}, 'body': {'type': 'string'}, 'index': {'type': 'integer', 'minimum': 0}, 'headline': {'type': 'string'}, 'to_index': {'type': 'integer', 'minimum': 0, 'description': "Destination for op='move'."}}}, 'description': "linkedin_carousel only. Two modes, one per call (do not mix). In-place edit: {index, headline?, body?} patches a slide's text without resending the whole slides[] array, and re-renders just that slide. Structural op: {op, index, ...} reorders the deck, where op is 'move' ({op:'move', index, to_index}), 'insert' ({op:'insert', index, headline?, body?}; index may equal the slide count to append), or 'delete' ({op:'delete', index}). Structural ops keep the slide text and the rendered images in lockstep, preserving the cover and closing slides; an inserted slide is rendered automatically. Out-of-bounds index errors; passing slide_patches on a non-carousel output errors."}, 'script_updates': {'type': 'object', 'description': 'Partial updates to the output.script JSON. Shallow-merged: keys present here replace the matching fields, keys absent are preserved. Available fields depend on platform: linkedin {hook, body, cta, structure}; twitter {hook_tweet, body_tweets[], landing_tweet, single_tweet}; longform {title, subtitle, body, pull_quote}; instagram {hook, caption, alt_text}; linkedin_carousel {cover_slide, slides[], cta_slide}.'}, 'regenerate_hooks': {'type': 'integer', 'minimum': 1, 'description': 'Generate this many fresh alternate opening lines for the output and return them as hook_variants for you to present. The body stays put. After the user picks one, splice it with apply_hook_variant_index. Metered like a short generation.'}, 'apply_hook_variant_index': {'type': 'integer', 'minimum': 0, 'description': 'Splice an existing hook_variants[N] into the live hook. 0-indexed. Cheaper than rewriting the caption by hand. Errors if the index is out of bounds.'}}}
入力スキーマ
{'type': 'object', 'required': ['subject'], 'properties': {'lens': {'enum': ['mainstream', 'emerging', 'investment'], 'type': 'string', 'description': "Ranking posture. 'mainstream' (default) = authority-weighted. 'emerging' = inverts saturation to surface low-coverage, pre-mainstream signal. 'investment' = lifts stories carrying funding/raise/round/term-sheet markers."}, 'count': {'type': 'integer', 'maximum': 15, 'minimum': 3, 'description': 'How many developments / narratives to return (3-15). Default ~5-10.'}, 'window': {'type': 'string', 'description': "Recency window: '24h' | 'week' | 'month' | 'quarter' | 'year'. 'this week' maps to 'week'. Overrides the niche's default recency. A strong bias by default; pair with recency_strict for a hard cutoff."}, 'subject': {'type': 'string', 'maxLength': 200, 'minLength': 2, 'description': 'The subject/space to investigate (2-200 chars). Specific is better.'}, 'brand_id': {'type': 'string', 'description': "Binds this brand's voice, colors, offer, and CTA to the piece. Omit to use your default brand; on a multi-brand account pass the slug explicitly so a post about one product is not bound to another brand's identity. niche_whoami lists your brands."}, 'platform': {'type': 'string', 'description': "Optional publish target (linkedin / x / instagram) that shapes each narrative's publish_hook."}, 'synthesis': {'enum': ['none', 'narratives', 'patterns'], 'type': 'string', 'description': "'narratives' = N non-obvious publishable threads across the slate. 'patterns' = the named movement (pairs with lens:'investment'). 'none' (default) = ranked slate only, no synthesis."}, 'recency_strict': {'type': 'boolean', 'description': "When true, `window` is a hard cutoff (out-of-window sources dropped before clustering) so 'nothing older than yesterday' is exact. Default false (bias only). Strict returns fewer, higher-confidence stories; use when precision matters more than breadth."}, 'source_quality': {'enum': ['strict', 'balanced', 'broad'], 'type': 'string', 'default': 'balanced', 'description': "Source-quality filter (niche-relative). 'strict' drops uncorroborated single-source silos that aren't primary/official or a niche authority, for high-trust answers only. 'balanced' (default) down-weights weak sources without dropping. 'broad' surfaces everything (incl. low-coverage emerging clusters), authority as a tiebreaker only."}, 'target_outputs': {'type': 'array', 'items': {'type': 'string'}, 'description': "Optional. The draft cells produced if you later draft a narrative into content (same cell list as niche_signal_scan, e.g. ['linkedin:image_post', 'x:thread']). Without this, a draft defaults to a single 'long_form_article'. Set it when you know the surfaces you want, so niche_draft_create yields them directly instead of needing niche_add_output after."}, 'idempotency_key': {'type': 'string', 'description': 'Optional. Stable key so a retry reuses the original run instead of billing a second; an identical query fired while one is still running is auto-deduped regardless.'}}}
入力スキーマ
{'type': 'object', 'properties': {'limit': {'type': 'integer', 'default': 25, 'maximum': 100, 'minimum': 1, 'description': 'Max sessions to return (default 25, max 100).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Skip this many sessions before returning, for paging through history. Default 0.'}, 'brand_id': {'type': 'string', 'description': 'Optional filter to sessions tied to one brand profile slot.'}, 'status_filter': {'type': 'string', 'description': "Optional status filter, e.g. 'cp1_awaiting_story', 'cp3_awaiting_review', 'complete', 'failed'. Omit for all."}, 'niche_contains': {'type': 'string', 'description': "Optional case-insensitive substring filter on the niche input, to find sessions on a topic (e.g. 'walnut')."}, 'include_outputs': {'type': 'boolean', 'default': False, 'description': "When true, also returns `recent_outputs`: the account's produced posts/images/reels across all sessions, newest first, each with its session_id, cell, a reachable asset_url, and publish state. Use it to locate a past asset (e.g. an image made on a prior run). Default false."}}}
入力スキーマ
{'type': 'object', 'required': ['session_id'], 'properties': {'svg': {'type': 'string', 'description': "Required when background='svg': the card's SVG markup as a string (<svg ...>...</svg>). You author the exact layout; the server rasterizes it to the cell's pixel dimensions. Use brand colors and fonts from niche_whoami to stay on-brand. Bundled fonts (set font-family to any by name; an unknown family or the generics 'sans-serif'/'serif'/'monospace' fall back to a real font so text always draws): sans (Inter, Geist, Open Sans, Montserrat, Lato, Poppins, DejaVu Sans), serif (DejaVu Serif, Lora, Playfair Display), mono (DejaVu Sans Mono, JetBrains Mono). Allowed: static shapes, paths, text, gradients, internal (#id) and inline data: references. Rejected with a named error: scripts, event handlers, foreignObject, external/file references, and DOCTYPE/ENTITY declarations. Max 512KB."}, 'cell': {'type': 'string', 'description': "Optional. Render at the canvas size for this cell:\n • linkedin:image_post: 1200×627 (1.91:1 landscape)\n • x:image_post: 1200×675 (16:9 landscape)\n • instagram:image_post: 1080×1350 (4:5 portrait)\n • linkedin:carousel / instagram:carousel: 1080×1080 (cover slide)\nWhen omitted: 1080×1080 universal-square asset stored under platform='image_card' (shared across all image cells)."}, 'scope': {'enum': ['full', 'recomposite', 'restore', 'reframe'], 'type': 'string', 'description': "'full' (default): render a new visual (background required). 'recomposite': free text edit on the existing image, with new headline/subhead/color/size composited over the retained background, pixels otherwise identical, synchronous, 0 credits. Use this for wording iteration; it avoids paying a re-render to change words. 'restore': bring back the prior image from history, free. 'reframe': free per-platform aspect variant that re-composites an existing card's retained background at the `cell`'s canvas size (x 16:9, instagram 4:5, linkedin 1.91:1), same text and grounding, synchronous, 0 credits. Requires `cell` (the target aspect)."}, 'subhead': {'type': 'string', 'description': "The smaller line under the header. On scope='full' it sets the subhead in the single render; on scope='recomposite' it edits it for no charge. Omit to keep the current one; pass '' to clear it."}, 'headline': {'type': 'string', 'description': "The bold header words; works for both backgrounds. Defaults to the post's `card_headline` (the short, sized-for-the-box line). Pass this to force exact text, e.g. a brand name leading it ('Acme drew a line'). It auto-fits the box and is never truncated."}, 'font_size': {'type': 'string', 'description': "Headline size. A relative word ('bigger'/'smaller'/'reset') steps from the current size and compounds; an absolute value (a number like 80, '80px', or '120%') sets it directly. Applies on scope='full' and scope='recomposite'. The response's font_changed/font_at_limit report whether it actually moved."}, 'background': {'enum': ['photo', 'brand_color', 'design', 'svg'], 'type': 'string', 'description': "Required for scope='full' (ignored otherwise): what's behind the text. 'photo' = a generated AI/photographic image (the actual picture; ~30 credits, ~30-90s, async). 'design' = a generated editorial design graphic that draws the argument (concept diagram / stat / pull-quote / comparison / method / abstract), on-brand and legible, no photo, no clichés, ~30 credits, async. 'brand_color' = a free, instant flat brand card (no generation). 'svg' = a free, instant card you author exactly as SVG markup (pass `svg`), rasterized at the cell's size; best for data, labels, and charts, and the only visual that works from a network-locked sandbox. No default: choose deliberately."}, 'session_id': {'type': 'string', 'description': "Session UUID that's reached cp3_awaiting_review or complete."}, 'text_color': {'type': 'string', 'description': "Text color as a name ('blue') or hex ('#ec4899'). Applies on scope='full' (set the color in the render) and scope='recomposite' (re-color for no charge). On background='brand_color' it colors the card text; omit to auto-pick a legible color from the background."}, 'design_color': {'type': 'string', 'description': "Optional, background='design': color control for the design card. Free text. Sets the card BACKGROUND when the phrase names a background or the card overall ('cream background', 'navy', 'on a green card'), or the ACCENT when it names one ('blue accent', '#0a3d62'); the rest stays on the brand's palette (or the default style when the brand has no kit). Omit to use the brand's palette."}, 'art_direction': {'type': 'string', 'description': "Optional free-text direction for a generated photo background (applies only when background='photo'). State the visual concept and any negatives, such as subjects or styles to avoid. Without it the background is generated from the story alone and tends toward category clichés (e.g. a robot for 'AI'); use this to steer toward a specific concept or an abstract, non-literal composition. The no-in-image-text rule still applies."}, 'estimate_only': {'type': 'boolean', 'default': False, 'description': 'If true, return {credit_cost} without rendering or spending. Use to learn the cost before committing.'}, 'text_position': {'type': 'string', 'description': "Where the overlay sits: 'top', 'center', or 'bottom'. On scope='full' it places the text in the render; on scope='recomposite' it moves the text on the existing image for no charge. Omit to keep the position the card was rendered at."}, 'design_concept': {'type': 'string', 'description': "Optional, background='design' only: free-text art direction for the design graphic, the layout/shape and concept (e.g. 'a 2-column comparison', 'an abstract composition, no literal imagery', 'a concept diagram of intended vs actual'). Omit to let the designer pick the shape that best carries the argument."}, 'background_color': {'type': 'string', 'description': "The background colour of a SOLID brand card (background='brand_color') as a name ('cream'/'navy'), a hex, or a brand keyword ('primary'/'accent'/'secondary'). Applies on a full brand_color render AND on scope='recomposite' (free, persists, so the colour does not snap back to the brand default on a later edit). (A generated-photo card recolours its text, not its photo background.)"}, 'needs_legible_text': {'type': 'boolean', 'description': "scope='full', background='photo' only. Set true when in-image text is genuinely the subject of the scene, which routes the render to a text-capable image generator. Defaults false (an atmospheric, text-free background, the usual choice)."}}}
入力スキーマ
{'type': 'object', 'required': ['session_id'], 'properties': {'cell': {'type': 'string', 'description': "Optional. Cell to tag the rendered Output row (linkedin:reel / x:reel / instagram:reel). All reels are 9:16 1080×1920 universal, so cell only affects bookkeeping, not pixels. When omitted: stored under platform='video' (shared across all reel cells)."}, 'tone': {'enum': ['default', 'punchy', 'direct', 'reflective'], 'type': 'string', 'description': "Script tone hint (scope='full'). Default 'default'. For direction beyond these four, use reel_direction."}, 'scope': {'type': 'string', 'description': "'full' (default): render a new reel (script plus stills plus voiceover, ~350 credits). 'recomposite': re-render presentation from the retained footage (captions on/off, caption_sync_mode, endcard_text/endcard_outro), at no charge, async. 'endcard': the same, limited to the endcard (endcard_text/endcard_outro/endcard_mark; use 'recomposite' to change captions). 'beat:N': re-render shot N's still (1-based, optional beat_direction and motion), ~15 credits; everything else is reused. Script changes (tone, voiceover words, music) require scope='full'. Partial scopes require a reel whose footage was retained, otherwise the response indicates scope='full' is needed."}, 'voice': {'type': 'string', 'description': "scope='full': voiceover voice for this render, overriding the brand kit's saved preference just this once. Use 'auto', 'male', or 'female', or a short hint like 'a warm female narrator'."}, 'length': {'enum': ['short', 'standard', 'long'], 'type': 'string', 'description': "scope='full': total runtime target the script is budgeted to hit. 'short' ~10-15s, 'standard' ~15-22s, 'long' ~25-40s. For an exact number of seconds, use target_duration_sec instead."}, 'motion': {'type': 'string', 'description': "scope='beat:N': the Ken Burns motion for the re-rolled shot ('zoom-in', 'zoom-out', 'static', or a plain steer like 'slow push-out'). Overrides that shot's existing motion. Omit to keep the current motion."}, 'session_id': {'type': 'string', 'description': "Session UUID that's reached cp3_awaiting_review or complete."}, 'endcard_mark': {'enum': ['logo', 'wordmark', 'headshot', 'text'], 'type': 'string', 'description': "Which brand mark stamps the endcard for this render, overriding the brand kit's saved preference just this once (e.g. end on the logo, not the name). Honored on scope='full', 'recomposite', and 'endcard'. Falls back to the brand name text when the chosen asset is not set on the kit."}, 'endcard_text': {'type': 'string', 'description': "scope='recomposite'/'endcard' only: the big endcard words (replaces the brand stamp text). Omit to keep the current endcard."}, 'endcard_color': {'type': 'string', 'description': "The endcard background color, a name or hex ('navy', '#0a3d62'), overriding the brand color on the closing frame just this once. The endcard text re-resolves to stay legible on it. Honored on scope='full', 'recomposite', and 'endcard'; ignored on 'beat:N'."}, 'endcard_outro': {'type': 'string', 'description': "scope='recomposite'/'endcard' only: the smaller endcard line under the mark. Omit to keep the current one; pass '' to clear it."}, 'estimate_only': {'type': 'boolean', 'default': False, 'description': 'If true, return {credit_cost} without rendering or spending. Use to learn the cost before committing.'}, 'music_enabled': {'type': 'boolean', 'description': 'Mix background music behind the voiceover. Default false.'}, 'beat_direction': {'type': 'string', 'description': "scope='beat:N' only: optional plain-language steer for the re-rolled shot ('warmer light, no people'). Omit for a fresh take on the same brief."}, 'reel_direction': {'type': 'string', 'description': "scope='full': free-form creative direction applied alongside tone, for steers the four tone words do not cover (pacing, narrative shape, mood), e.g. 'courtroom-drama narration, build to a reveal'. The free-form sibling of tone."}, 'music_direction': {'type': 'string', 'description': "scope='full': free-form style, genre, and tempo for the background music, e.g. 'calm lo-fi, no vocals'. Supplying it turns music on for this render. Music is generated audio, so a free re-render or shot re-roll cannot change it."}, 'captions_enabled': {'type': 'boolean', 'description': "Overlay caption text on each beat. Default true. With scope='recomposite': omit to keep the reel's current setting."}, 'caption_sync_mode': {'enum': ['beat', 'phrase'], 'type': 'string', 'description': "How captions track the voiceover. 'beat' (default): each beat's caption shows for that beat's spoken window. 'phrase': the caption reveals phrase-by-phrase in lockstep with the spoken words (caption text is taken from the spoken VO). Ignored when captions_enabled is false. No extra cost."}, 'needs_legible_text': {'type': 'boolean', 'description': "scope='full': force the text-capable still generator for every shot, for a reel whose stills carry readable words. Default false lets each shot pick the best generator on its own."}, 'target_duration_sec': {'type': 'integer', 'description': "scope='full': exact total runtime target in seconds (8 to 45). Wins over `length`. The script budgets each beat to land near this total; the delivered duration can vary by a second or two."}}}
入力スキーマ
{'type': 'object', 'required': ['session_id', 'from_cell', 'to_cell'], 'properties': {'to_cell': {'type': 'string', 'description': "Cell to copy the image onto (e.g. 'x:image_post')."}, 'from_cell': {'type': 'string', 'description': "Cell that has the image (e.g. 'linkedin:image_post')."}, 'session_id': {'type': 'string'}}}
入力スキーマ
{'type': 'object', 'required': ['session_id'], 'properties': {'reason': {'type': 'string', 'description': 'Optional. Surfaced as session.error_message.'}, 'session_id': {'type': 'string'}}}
入力スキーマ
{'type': 'object', 'required': ['session_id'], 'properties': {'format': {'enum': ['markdown', 'json'], 'type': 'string', 'default': 'markdown', 'description': 'Output shape. Default markdown.'}, 'session_id': {'type': 'string'}}}
入力スキーマ
{'type': 'object', 'required': ['session_id', 'to'], 'properties': {'to': {'enum': ['story', 'angle'], 'type': 'string'}, 'session_id': {'type': 'string'}, 'acknowledge_discard': {'type': 'boolean', 'description': 'Required to revert a cp3/complete session; confirms you accept discarding its finished, paid-for drafts.'}}}
入力スキーマ
{'type': 'object', 'required': ['session_id'], 'properties': {'view': {'enum': ['status', 'full'], 'type': 'string', 'default': 'full', 'description': '`status` (lean): a few-hundred-byte control envelope of status, phase, picked ids, `content_versions` (per-section change counters), `counts`, `output_ids`, cost, and next_step. Use this for the poll loop. `full` (default): the complete state with stories/angles/outputs. Fetch `full` once when a content_version moves, rather than re-shipping the full state every poll. Pairs with wait plus since_status.'}, 'wait': {'type': 'integer', 'default': 0, 'maximum': 30, 'minimum': 0, 'description': 'Long-poll for up to N seconds (0-30) waiting for the session state to change. Returns immediately if the state already differs from `since_status` (or if the session is awaiting a checkpoint / complete / failed). Drops agent token burn from N polls to 1 wait. Default 0 (no wait, behave as before).'}, 'wait_for': {'enum': ['change', 'checkpoint', 'render', 'synthesis'], 'type': 'string', 'description': 'Alias for `wait_until` (same values and semantics). Use either; if both are set, `wait_until` wins. An unrecognized value is rejected (not silently ignored), and it only takes effect with `wait` > 0.'}, 'session_id': {'type': 'string', 'description': 'session_id from niche_signal_scan.'}, 'wait_until': {'enum': ['change', 'checkpoint', 'render', 'synthesis'], 'type': 'string', 'default': 'change', 'description': "What the `wait` long-poll resolves on. `change` (default): any status change vs since_status, a reached checkpoint, or a status-less change (synthesis fill, render completion, new output). `checkpoint`: only an actionable checkpoint (cpN_awaiting_* / complete / failed), skipping the noisy intermediate cpN_generating and generating_* transitions, so you get one wake per checkpoint. `render`: a reel/image render marker settles (done|failed). Use this after niche_render_reel / niche_render_image_card, since a render leaves status unchanged and won't wake a `change` wait on status alone. Works on a session already at complete (the normal case, since renders are post-completion add-ons): the wait holds on the render marker, not the session status. `synthesis`: a niche_intelligence_query's synthesis lands (narratives or a shortfall_note). Synthesis filling is not a status change, so block on this in one call instead of busy-polling."}, 'since_status': {'type': 'string', 'description': "Used with `wait` (wait_until='change'). The status the caller last saw; the wait returns as soon as session.status differs. If unset, any state-change event wakes the wait. Note the pipeline has paired vocabulary: a transient `cpN_generating` (checkpoint about to produce) and a `generating_*` work status (e.g. cp2_generating maps to generating_angles). To skip both and wake only at the next actionable stop, use wait_until='checkpoint' rather than chasing since_status through the intermediates."}, 'include_unpicked': {'type': 'boolean', 'default': False, 'description': "When true, return the full candidate set even after picks have been made. Default false (sparse: only the picked story / angle come back). Meaningful only with view='full'; ignored in view='status' (the lean envelope never carries the candidate slate)."}, 'include_status_glossary': {'type': 'boolean', 'default': False, 'description': "When true, response includes `status_glossary[]`, the full 17-status state-machine descriptor list with phase, hint, and `actionable` (whether a wait_until='checkpoint' wakes for it) per status. Useful on the first call of an agent session so the agent caches the full map; leave false on subsequent polls. Default false."}}}
入力スキーマ
{'type': 'object', 'required': ['niche'], 'properties': {'niche': {'type': 'string', 'maxLength': 200, 'minLength': 2, 'description': 'Niche / beat description (2-200 chars). Specific is better.'}, 'density': {'enum': ['minimal', 'balanced', 'essay'], 'type': 'string', 'default': 'balanced', 'description': 'How tightly to pack visual formats (carousels especially).\n • minimal: split a dense argument across more, shorter slides; favors skimmability.\n • balanced (default): the standard per-slide caps.\n • essay: permissive; allows longer slides/sections.\nOver-cap slides auto-split on sentence boundaries (never mid-sentence); over-280 tweets split into a chain regardless.'}, 'recency': {'type': 'string', 'description': "Optional recency window that constrains discovery to fresh sources. One of '24h' | 'week' | 'month' | 'quarter' | 'year' (aliases: 'today'/'day'→24h, 'this week'→week, etc.). Use '24h' for 'today only / breaking'. Putting the demand in the niche string does not constrain recency; use this param. Omit to use the niche's default window."}, 'brand_id': {'type': 'string', 'description': "Binds this brand's voice, colors, offer, and CTA to the piece. Omit to use your default brand; on a multi-brand account pass the slug explicitly so a post about one product is not bound to another brand's identity. niche_whoami lists your brands."}, 'estimate_only': {'type': 'boolean', 'default': False, 'description': 'When true, return the discovery credit cost without starting a run or holding a reservation. Use it to quote a price before committing.'}, 'recency_strict': {'type': 'boolean', 'description': "When true, `recency` is a hard cutoff: sources outside the window are dropped before clustering, so 'nothing older than yesterday' is honored exactly. Default false: the window is a strong bias but older corroborating sources can still attach to a fresh cluster. Set true when exactness matters more than slate depth (strict can thin the slate)."}, 'source_quality': {'enum': ['strict', 'balanced', 'broad'], 'type': 'string', 'default': 'balanced', 'description': "How aggressively to filter the slate on source quality (niche-relative; never penalizes a small publication that is the authority for the niche).\n • strict: drop uncorroborated single-source silos that aren't a primary/official or a niche authority; surfaces only well-sourced stories. Can thin the slate.\n • balanced (default): down-weight weak sources, don't drop.\n • broad: surface everything, including low-coverage emerging clusters, with authority as a tiebreaker only.\nUse strict for a high-trust brief, broad to scout early signal."}, 'target_outputs': {'type': 'array', 'items': {'type': 'string'}, 'description': "Output cells to generate (platform×content_type matrix). Each cell is a 'platform:content_type' string or a cross-platform content type. Valid cells:\n • linkedin:text_post: short LinkedIn post (text only)\n • linkedin:image_post: LinkedIn post plus 1.91:1 image card\n • linkedin:carousel: multi-slide carousel\n • linkedin:reel: LinkedIn-native vertical video\n • x:single_tweet: standalone tweet\n • x:thread: multi-tweet thread\n • x:image_post: tweet plus 16:9 image card\n • x:reel: tweet plus 9:16 video\n • instagram:image_post: caption plus 4:5 image card\n • instagram:carousel: multi-image swipe\n • instagram:reel: caption plus 9:16 reel\n • long_form_article: universal essay (Substack/blog)\n\nA bare platform name also works and maps to that platform's default cell: 'x'/'twitter'→x:single_tweet (pass 'x:thread' for a thread), 'linkedin'/'li'→linkedin:text_post, 'instagram'/'ig'→instagram:image_post, 'longform'→long_form_article. The resolved cells are echoed back as target_outputs.\nDefaults to ['linkedin:text_post']; request only the cells you need (each additional cell adds generation cost)."}, 'idempotency_key': {'type': 'string', 'description': 'Optional. A stable key for this logical scan: a retry with the same key reuses the original run instead of starting (and billing) a second. Even without it, an identical scan fired while one is still running is auto-deduped.'}, 'thinking_budget': {'enum': ['fast', 'balanced'], 'type': 'string', 'default': 'balanced', 'description': "Agent-side polling control. A full editorial workflow is structurally 15-20 tool calls (scan, poll, poll, poll, pick, poll, pick, poll, read). Use this to tune how many of those calls collapse into single waits.\n • fast: scan blocks briefly (up to ~45s, under the tool-call timeout) for CP1 and returns the stories inline when they land in time. One call instead of many polls. Cheaper agent tokens, smaller cognitive surface. If CP1 isn't ready by the cap it still returns the session_id (status=discovering); poll niche_session_state from there, the run is never lost. Pick when you just want stories and the user is waiting.\n • balanced (default): scan returns immediately with session_id; the agent polls niche_session_state (with wait plus since_status long-poll)."}, 'target_platforms': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Optional. A flat platform list (linkedin, linkedin_carousel, twitter, longform, instagram), coerced into target_outputs cells. Prefer target_outputs.'}, 'profile_overrides': {'type': 'object', 'description': 'Optional. Deep-merge these overrides onto the persisted profile for this run only. Use case: same brand, different register for a specific piece (e.g. a product-launch voice over an editorial one). Requires brand_id.'}}}
入力スキーマ
{'type': 'object', 'properties': {'url': {'type': 'string', 'description': 'URL to scrape post-shaped text from. Substack / Medium / blog homepages work best; X / LinkedIn profile pages are supported but yield less per-snippet text.'}, 'posts': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Post-shaped text snippets the user wrote. ≥80 chars each; 3-10 snippets is the sweet spot for primitives extraction.'}, 'brand_id': {'type': 'string', 'description': "Which brand's voice to ingest into. Voice is brand-scoped: omit for the default brand, or pass a brand_id (from niche_brand_profile_get) to set up that brand's voice without touching another brand's. A brand with no voice of its own falls back to the default voice at generation time."}, 'archetype': {'type': 'string', 'default': 'agent_ingest', 'description': "Optional label for how the profile was acquired; tags the VoiceProfile.archetype field. Defaults to 'agent_ingest'."}, 'overwrite': {'type': 'boolean', 'default': False, 'description': 'Whether to replace an existing VoiceProfile. Default false: first run wins, subsequent runs are no-ops unless explicitly opted in, so an automated re-run does not overwrite a hand-curated voice.'}}}
入力スキーマ
{'type': 'object', 'properties': {'include_test': {'type': 'boolean', 'default': False, 'description': 'Include scratch and experimental brand slots in available_brands. Default false so only real brands are listed and an agent does not bind a throwaway by accident.'}, 'include_all_brands': {'type': 'boolean', 'default': False, 'description': 'List every brand on the account in available_brands. Default false: only the active brand is returned (with other_brand_count) so a single orienting call does not enumerate the whole roster. Set true when you genuinely need to pick among multiple brands.'}, 'include_brand_assets': {'type': 'boolean', 'default': False, 'description': 'Include usable brand asset URLs (logo, wordmark) in brand_palette. Default false: the palette returns colours and fonts plus logo_available / wordmark_available booleans, and the renderer fetches the actual asset when a render needs it. Set true only if you need the asset URL directly.'}}, 'additionalProperties': False}
最近のツール変更
類似のMCPサーバー
Heista
Analyzes video advertising, builds brand intelligence profiles, researches markets, and generates advertising scripts and creativ…
VarynForge
Supports SEO research and content planning through niche analysis, competitor and keyword research, opportunity discovery, and wr…
AfterLaunch: the agentic growth marketing engine
Runs growth-marketing workflows covering SEO, AI-search visibility, competitor analysis, content planning, distribution channels,…
Jargon Translator
Provides corporate-jargon translation, AI brand visibility checks, AI crawler indexing files, and routed research across structur…
particle-pro
Searches and analyzes podcast episodes, transcripts, speakers, companies, topics, clips, rankings, sponsorships, and entity menti…
BetterPost
Gathers timely stories from news, web, and social sources and generates, transforms, and manages publishable content.
agents
Offers paid agents for brand visibility checks, web data extraction, webpage reading, marketing video production, and deliverable…
Social Fetch
Retrieves public content and metadata from social networks, music services, Facebook Marketplace, events, profiles, posts, commen…