# Siren

**One brief becomes an on-brand post, published.**

Siren is a deterministic marketing engine behind a remote MCP server: an agent
hands it a plain-language brief, and Siren writes the copy, renders the asset
from code with the workspace's Brand DNA (colors, type, logo, voice, products),
writes a caption per platform, and publishes or schedules it to the channels
that workspace connected.

That is the whole job. Not a canvas, not a scheduler — **brief in, on-brand post
out, live on the customer's channel.** The same brief gives the same asset.

| | |
|---|---|
| Server | `https://mcp.mysiren.ai/mcp` — Streamable HTTP, OAuth 2.1 + PKCE |
| This file | `https://mcp.mysiren.ai/skill.md` |
| Tool catalog | `https://mcp.mysiren.ai/tools` · registry name `ai.mysiren/siren` |
| Human docs | `https://mysiren.ai/docs/mcp` |
| Brand Memory | **Not on MCP.** Agents read and patch Brand DNA only. |

## Connect

Claude Code:

```
claude mcp add --transport http siren https://mcp.mysiren.ai/mcp
```

Claude (claude.ai): Settings → Connectors → Custom → paste
`https://mcp.mysiren.ai/mcp`.

Cursor: Settings → Tools & MCP → Add a remote server, or put this in
`~/.cursor/mcp.json` (or `.cursor/mcp.json`):

```
{ "mcpServers": { "siren": { "url": "https://mcp.mysiren.ai/mcp" } } }
```

ChatGPT: Developer Mode on → Settings → Apps & Connectors → Create → URL
`https://mcp.mysiren.ai/mcp`.

Grok: [grok.com/connectors](https://grok.com/connectors) → New → Custom, or:

```
grok mcp add --transport http siren https://mcp.mysiren.ai/mcp
```

The agent opens a consent page, the human taps Authorise, done. There is no API
key to paste. **Posting stays off until the human turns on Allow posting on that
consent screen** — see below, it matters for half the tools.

## Reach for the tools in this order

**1. `get_account` first.** It is how you learn what this connection is for: the
workspace plan, the allowed `asset_type` values, the allowed channels, the
connected accounts, the auto-post setting, and the daily limit. Call it before
you generate anything. **Empty scope lists mean unrestricted, not missing** — a
fresh consent mints a key with empty lists on purpose and can still render and
post. There is no plan gate on video: credits are the only meter.

**2. `get_brand_dna` next, and read its `checks`.** A film is only as specific as
Brand DNA. When `checks` says `thin_brand_dna`, fix that first: ask the human
for the brand's site and paste it on Brand DNA, or fill what you already know
(`update_brand_dna` for name, `brand_kind`, overview, colours;
`upload_product_screen` for the brand's own screens or work). A `palette` check
means the typed accent is not in the brand's own logo or screens: ask which is
right before you render. Set `brand_name` when the workspace is named after a
person, and `brand_kind` (product, creator, agency, local, personal): creators
get different films than products.

**3. Prefer `create_campaign`.** The planner picks the film and writes the body
from Brand DNA. Use `render_asset` when the human names a film or you have a
body in hand. Images, ads and creatives always go through `create_campaign`
with `asset_type=card`: Siren writes the picture in the brand and paints it.
`render_asset` is films only, and Studio cards go through `studio_card`.

**Film knobs on `create_campaign`.** `voiceover` (true narrated, false motion
+ music; if the human never said, ask once), `direction` (one line on mood,
pace, motion and music), `music` (a bed id) and `media` (URLs from
`upload_media` the film shows). Set them instead of hoping the brief's words
carry them. Images ignore them: Siren writes and paints images from the brief.

**4. Wait, don't spin.** Every render returns `estimated_seconds` and
`poll_after_seconds`; `get_run` returns `progress`, `eta_seconds` and a new
`poll_after_seconds`. Wait that long between polls. When the film is done,
read `film_check`. It is advice: if `ok` is false, show the human the video and
the findings and let them decide. Re-render at most once, and only for a defect
you can name in what you sent (a missing image, a wrong name). If the run has
`notices`, pass them to the human: the asset is made as asked, and the note
says where the ask fights their brand (a dark night scene on a warm brand).

## Turning what the human gives you into Siren input

Siren reads Brand DNA, the brief and uploaded media. You do the reading of
anything else, then hand Siren the result:

- **A link** ("make a campaign from this site"): read the page yourself. Put the
  facts into `update_brand_dna` (name, overview, colours, `brand_kind`), the logo
  into `upload_logo`, and the product's own screenshots into
  `upload_product_screen`. Then `create_campaign` with a brief that carries the
  page's one real claim.
- **A PDF or doc** (a deck, a launch note): read it yourself and write its key
  facts into the brief, and into `update_brand_dna` when they describe the brand.
- **Photos for an ad creative** ("use this photo", "put our product here"):
  `upload_media` each one, then `create_campaign` with `asset_type=card`, `media`
  set to the returned URLs, and the placement in the brief ("the mug in the
  top-left"). Siren paints with them as given. For a photo the brand will use
  again, also `upload_product_screen` it into Brand DNA.
- **Photos or videos for a film** ("make a video using these"): `upload_media`
  each one, then `create_campaign` with `media` set to the returned URLs, or put
  them in a film body's media slots with `render_asset` (`describe_film` lists
  the slots).
- **Photos of a character** ("a mascot that looks like this"): `create_mascot`
  with the clearest single picture as its image URL. 100 credits.
- **A change to a finished creative** ("make the hair pink"): `edit_asset` with
  the run id. Chain edits freely; nothing else drifts.

## How generation works

Three paths, and you never pick the machinery:

- **Images are painted.** A still brief goes to Shakespeare, who writes the
  storyline, then Van Gogh, who paints it in the workspace's Brand DNA. There is
  no image template to choose.
- **Films are rendered from code.** Deterministic React/Pulse → MP4: the same
  body always renders the same film. Use `list_films` and `describe_film` to
  pick the format. Videos are on every plan; credits are the only meter (25 a video, 50 platinum).
- **Studio cards are their own family.** Deterministic React data cards through
  the `studio_*` tools — separate from stills and films, on every plan.

It does **not** report whether posting is allowed — nothing does. You find that
out when a posting tool refuses, so read the next section before you promise the
human a published post.

**2. `clarify_brief` if the human's request is vague.** Pass their words
unchanged. It returns `ready` plus follow-up questions. Skip it for a clear
brief.

**3. Generate.** `create_campaign` is the normal path — it runs the whole
pipeline (plan, on-brand copy, deterministic render, verify, caption). It spends
credits and returns a run immediately with `status` `queued`.

- `render_asset` is the advanced door: it renders pre-formed SirenSeed bodies
  with no planner. Films only; a still returns 422 `stills_use_a_brief`.
- `studio_card` renders one of Studio's React data-card types. Call
  `studio_templates` first so the `body` matches the type's schema.

**4. Poll `get_run` until terminal.** `queued` and `running` are not answers.
Poll `get_run` with the `run_id` until `status` is `succeeded` or `failed`, then
read the asset URLs, the caption, and any error out of the result. `list_runs`
finds a run you lost.

**5. Publish.** `post_run` queues the post behind a short publish window
(about two minutes, cancellable) and fires it — poll `get_run` after that and
read `posts[].url` for the live permalink. Pass `caption` to own the words
(verbatim); omit it and Siren writes the caption at queue time, so `get_run`
shows what will post before it does. `schedule_post` queues a finished run
at a time in the human's timezone. `create_campaign` also takes `scheduled_at`,
which queues the post for when the render succeeds. Platforms fire
sequentially: X, LinkedIn, Instagram, TikTok, YouTube.

## The 44 tools

Read-only tools change nothing:

| Tool | What it does |
|---|---|
| `get_account` | Workspace plan, allowed asset types and channels, auto-post, daily limit. Call first. |
| `list_runs` | Runs, newest first: status, brief, channel, assets, caption. Page with `offset` for every video and image. |
| `get_run` | One run by id: status, output text, asset URLs, caption, errors. |
| `list_films` | Every live film master: what it is, when to hire it, runtime, and whether the plan can render it. 25 credits each. |
| `list_platinum_films` | The platinum shelf: studio-grade films at 50 credits each, every time. Only for a big launch. |
| `list_styles` | The still style library by shelf (social ads, posters, launch cards, thumbnails): what each style is for, ranked, with this brand's recent looks marked. Pin one by writing its id in the brief. |
| `describe_film` | One film's full contract: body schema, reference body, seed envelope, media slots, brand prefills. |
| `clarify_brief` | Is this brief specific enough? Returns `ready` + questions. |
| `get_brand_dna` | The workspace's Brand DNA. Not Brand Memory. |
| `list_product_screens` | Product screenshots in Brand DNA. |
| `studio_templates` | Studio's card types with family, description, body schema. |
| `get_mascot` | The brand mascot: status, hero, tries left, every pose frame. |
| `list_people` | The people library: team faces with name, role, status. |
| `list_media` | Every photo and video the agent uploaded, with URLs to reuse. |
| `list_brand_videos` | The brand's own clips: what each shows, best moments, when to use it. |
| `get_performance` | How the posts did: totals and the top posts (views, likes, replies). |
| `list_posts` | Every post sent or queued: platform, status, live URL, counts by status. |
| `studio_threads` | The workspace's Studio videos, newest first: title, shelf, current version, who started it. |
| `studio_get` | One Studio video: the chat with every diff, all versions, the current one. |

State-changing tools — these write, spend credits, or publish:

| Tool | What it does |
|---|---|
| `create_campaign` | Full pipeline from a brief. Spends credits. |
| `render_asset` | Render pre-formed film bodies directly. Films only. Spends credits. |
| `upload_media` | Store a photo or video from a URL or base64; returns a workspace-owned URL for any media slot. |
| `edit_asset` | Change a finished ad creative in place ("make the hair pink"): only that changes, every other pixel stays. 3 credits, about 15 s. |
| `update_brand_dna` | Patch Brand DNA — send only the keys to change. |
| `upload_product_screen` | Upload a screenshot into Brand DNA (stored on R2). |
| `update_product_screen` | Patch tags on an existing screenshot. |
| `delete_product_screen` | Remove a screenshot. Does not touch Brand Memory. |
| `upload_logo` | Set the brand logo (light or dark) from a URL or base64. |
| `create_mascot` | Start the mascot from a description or a reference picture. 100 credits. |
| `mascot_step` | Keep the hero, paint it again, or add a pose. |
| `add_person` | Add a face to the people library. Only people who agreed. |
| `delete_person` | Remove a face from the people library. |
| `upload_brand_video` | Add a demo or footage to Brand DNA; Gemini reads it, films play it when a brief names it. |
| `delete_brand_video` | Remove a clip from Brand DNA. |
| `delete_media` | Delete one upload by its URL. |
| `buy_credits` | A Stripe checkout link for more credits. The human pays; the call charges nothing. |
| `studio_card` | Render a Studio data card. Optional live post. |
| `studio_automate` | Create a Studio profile with webhook delivery. |
| `studio_schedule` | Render a Studio card, then queue a scheduled post. |
| `studio_start` | Start a Studio video from a description or an App Store link. Siren composes the first take; nothing is charged yet. The human sees the same thread. |
| `studio_edit` | Change a Studio video by asking ("shorter hook", "make it vertical"). Returns the reply, the diff and the new version. Attach upload_media URLs to use them. No credits until export. |
| `studio_export` | Render a Studio video to a file. 25 credits (platinum 50). Poll get_run for the file. |
| `post_run` | Publish a finished run behind a short publish window. Live and public. |
| `schedule_post` | Queue a finished run for a named time. |

## Films: browse, then fill the body

Video is deterministic: the same body always renders the same MP4. To hire a
specific film instead of letting the planner pick, read the catalog first.

**Browse.** Films sit on job shelves: `launch`, `feature` (one feature or
update), `explain` (what the product does), `brand` (story, manifesto, mood),
`ai` (AI or agent product), `proof` (a number, testimonials), `app` (mobile),
`shop` (products, food, property), `people` (team or mascot). Call
`list_films(job=..., goal="the human's words")` and hire from the top of the
page; `page=2` for more, `search="phone"` to find by word, `all=true` for every
film. Or fetch the whole library as one file and read it in steps:
https://api.mysiren.ai/api/v1/films.md (also `.txt`, `.json`, `?job=launch`).
Then `describe_film` for the one you want: its body JSON Schema, a reference
body, the runtime, and the rules.

**Platinum.** A platinum film matches a top motion studio's craft, told as the
brand's own story. It costs **50 credits every time** (a normal film is 25) and
its id ends in `-plt`. Offer one only when the human is making a big launch they
genuinely want to go far; everyday posts and normal budgets get the normal
films. `list_films` keeps platinum out of its pages and flags it in a
`platinum` section; `list_platinum_films` is the shelf. Say the price before you
render. Platinum needs a paid plan or a credit top-up: a complimentary plan
sees it but gets a 402 `platinum_needs_paid_plan`; relay it and offer a normal
film. Films also carry `voice`: `MM` is motion and music only (never ask about
voice-over, no narration), `MVM` is motion, voice and music.

**Do not repeat.** Each film shows `made_recently` when this brand already got
it. Hire a different film on the same shelf unless the human asked for that
one by name; `describe_film` on a repeat names the films next to it. Every
shelf has several films and they are all good: fit the job, not the name.

**Fit.** `list_films` and `describe_film` say `fits_this_brand`, what is
`missing`, and `warnings` for this workspace (a product-UI film on a creator
brand, a people film with no people). Films that fit float to the top. A render
the film cannot use (a video URL in an image slot, a stills-led film with no
images anywhere) comes back as a 422 `film_does_not_fit` with the `fix` and
`films_that_fit`. Nothing half-works.

**Fill.** Write only the `body`, the copy fields the schema requires. The
engine fills the rest: brand colours, logo, typeface, aspect, duration and
narration come from the workspace's Brand DNA. Omitted media fields fill from
the brand's own screen library, so you rarely need to pass images at all.
Leave media fields empty rather than inventing links.

**The director.** Every `render_asset` film body is read by Siren's director
before it renders. It keeps the film you picked and your lines that are
specific to the brand; it rewrites lines that are empty, generic, copied from
the reference body, or that name Siren on another brand, from Brand DNA. It
never touches media fields. What it changed comes back as `director` on the
render and `director_notes` on `get_run`. Pass `direction` ("dark, club
energy, fast") to steer words, pace and music; `music` names a bed outright.

**Override.** Any Brand DNA key can be overridden per render with
`brand_override` on a `render_asset` item (null clears it). `voice_id` controls
narration: omit to inherit the brand voice, `""` for silence.

**Media by link.** `upload_media` stores a photo or video (https URL or bytes)
and returns a workspace-owned URL. Put it in any media slot `describe_film`
lists — screens, stills, wheel photos. A media slot is only a placeholder for
the URL; the renderer reads it directly. Media fields already set in the body
always win over the prefill.

**Video clips.** Alchemy and Gallery play motion from sprite sheets. Upload the
video with `upload_media`, then put its URL in the body's `videoClips` — the
engine cuts it into sheets at render time and plays it in the clip tiles. Only
workspace-owned URLs are accepted; anything foreign is dropped, not fetched.

**Pin a film on the brief door.** `create_campaign` takes an optional
`output_type` (a film id from `list_films`); the planner still writes the body.

## Any brand, not only this workspace's

Siren makes films and stills for any brand the human names: their own, a
client's, a side project, a brand everyone knows. Paid plans only for video.

1. Look the brand up first: its real site, its colours as hex, its typeface,
   and a logo file URL (https PNG, SVG or JPG, ideally transparent). Use the
   brand's MARK or app icon (Duolingo's owl, Nike's swoosh), not the
   wordmark: the film already sets the brand name in type, so a wordmark
   prints the name twice.
2. On `create_campaign`, pass `brand`:

   ```json
   {"brief": "a motion graphics video for the new Acme Runner shoe",
    "asset_type": "video",
    "brand": {"name": "Acme", "site": "acme.com",
              "logo_url": "https://acme.com/logo.png",
              "primary": "#111111", "accent": "#FF4400",
              "sells": "running shoes"}}
   ```

   On `render_asset`, put the same object on the item as `brand_override`.
3. That brand starts from a clean slate. Nothing of this workspace is used:
   not its logo, colours, mascot, font, X handle, product screenshots,
   painted stills or memory. Only what you send, plus media you upload with
   `upload_media` for that brand.
4. Omit `brand` and Siren uses this workspace's Brand DNA as usual.

Keys: name (required), site, logo_url, logo_url_dark, accent, primary,
secondary, tagline, font, x_handle, sells (one line: what they make).
Put the brand's signature colour in accent: films are painted in it. primary
is its dark or ink colour (Duolingo: accent green, primary near-black). A bad colour or a non-https logo returns a 422 that says
which key to fix.

**The logo is not optional.** A film without the brand's mark looks like a
generic video, so Siren refuses a brand it cannot paint:

| Error | What it means | What to do |
|---|---|---|
| `brand_logo_required` | no `logo_url` | find the mark: the image in their site header, the file on their `/brand` or `/press` page, or the app icon in a store listing |
| `brand_logo_unreachable` | the URL 404s, times out or is not public | open it yourself; pass the file their own site serves |
| `brand_logo_unreadable` | the bytes are not an image | it is a damaged file or an HTML page, not a picture; pass a direct PNG, SVG, JPG or WebP |
| `brand_colours_required` | primary and accent are the same | read both off their site: accent is their button colour, primary their dark or ground colour |

Never invent a logo URL, never pass a screenshot of their homepage as the
logo, and never reuse this workspace's logo for another brand. If you cannot
find the mark, say so to the human and ask for the file instead of guessing.

## Posting needs Allow posting

Six tools touch publishing, and every one of them refuses with an error when the
connection lacks the `post` scope:

| Tool | When it needs `post` |
|---|---|
| `post_run` | Always — it queues a live publish. |
| `schedule_post` | Always. |
| `studio_schedule` | Always. |
| `create_campaign` | Only when you pass `scheduled_at`. |
| `studio_card` | Only when you pass `post` true. |
| `studio_automate` | Only when `delivery` carries `social_account_ids`. Telegram and Discord delivery are not posts and stay open. |

The refusal means the human has not turned on **Allow posting**. `get_account`
will not warn you first — the refusal is how you find out. Do not retry it, do
not work around it — tell them to reconnect the agent and turn Allow posting on,
then stop.

**Confirm with the human before any live post.** `post_run` and `schedule_post`
put words on a public channel under their name. Render first, show them the
asset and the caption, and wait for a yes. Posting also requires a channel they
connected themselves in the Siren dashboard; the agent cannot connect one.

## Plans: credits are the only limit

- **MCP and films are on every plan, Spark and Free included.** The agent door is
  not a paywall and neither is video.
- A still is 10 credits, a film 25 (voiceover or music-only is the same 25), a
  platinum film 50. Every generate spends them. Read the balance off
  `get_account` and do not burn a run on a brief the human has not agreed to.
- Short on credits: `buy_credits` returns a Stripe link for the human. The 50
  credit pack is $9.99 once, no subscription, and covers one launch film.

## No account at all: the launch order

A human who has no Siren account and no MCP connection can still get one launch
video through you, with nothing but HTTP:

```
POST https://api.mysiren.ai/api/v1/launch/order
{"email": "<the human's email>", "site": "https://their.app", "brief": "<what the video should say>"}
```

The answer carries `checkout_url` ($9.99 once, 50 credits) and `poll`. Give the
human the checkout link, then `GET poll` every 10 seconds: `awaiting_payment`,
`reading_brand`, `rendering`, `done` with `video_url`. Optional `film` pins a
film id from https://api.mysiren.ai/api/v1/films.md; otherwise Siren picks one
for the brief. An email that already has an account gets `account_exists`:
send that human to connect Siren here instead.

## What agents cannot do

- **Brand Memory is not on this surface.** No tool reads or writes the memory
  ledger. Do not promise it.
- Agents cannot connect or disconnect social channels.
- Agents cannot charge a card. `buy_credits` returns a checkout link the human
  opens and pays.
- `post_run` posts to a channel the human already connected, nothing else.

Errors from the API come back structured and verbatim (`402` plan gates, `403`
scopes, credit and quota failures). Read them, tell the human what they say, and
do not retry a refusal.

## Five prompts to try

Copy any of these into Cursor, Claude Code, Claude, or ChatGPT with Siren
connected. Replace the angle, keep the shape.

**1. Post this** — the everyday one. Works on every plan.

```
post this. Look at what I changed in this repo today, write one X post about
the sharpest thing in it, make the still in our brand, and show me the image
and the caption before you publish. Do not post until I say go.
```

**2. Launch week campaign** — the thing a solo founder would otherwise spend a
Sunday on. Stills and cards need no plan upgrade.

```
Make me a launch week campaign for [feature]. Five posts, one per weekday, in
our brand: what shipped, why it matters, the before/after, one customer angle,
and a closing post. Render the first one and stop — I want to see it before you
schedule anything. If Allow posting is on, schedule the rest for 9am my time on
X, and use LinkedIn for the closing post.
```

**3. Changelog post** — keep this one short, it runs every release.

```
Turn this changelog entry into a post for LinkedIn: "..." Keep our voice, lead
with the outcome, not the implementation. Give me a still sized for LinkedIn.
```
Then, on the next release, just: `same as last time, for this release`.

**4. Founder story reel** — a film of how the product got here. 25 credits,
on every plan.

```
I want a founder story reel: 30 seconds, motion graphics, no talking head —
where this product came from, the moment we decided to build it, and where it
is going. Our colors and type. Use the launch screenshot as the product beat.
Render it and show me before you post it.
```

**5. Competitor comparison carousel** — the one that gets saved and reshared.

```
Make a comparison carousel: us vs [competitor]. Pick the five differences that
actually matter to a buyer, one per slide, honest about what they do better.
Call studio_templates first and use a comparison-shaped data card, or a carousel
brief if that fits better. Render slide one and show me the text before you
render the rest.
```

## If something is missing

`get_account` did not return a plan you can publish on, the human has not
connected a channel, or a tool returned a plan or scope error: say exactly that
and stop. The two fixes are always the same — turn on Allow posting on the
consent screen, or connect the channel in the Siren dashboard. Siren's own docs
for humans are at `https://mysiren.ai/docs/mcp`.

## Stills: pick a style

Every still (asset_type `card`) is painted in one style from Siren's library.
Siren picks one and avoids the looks this brand just got. To choose it
yourself: `list_styles(section="social-ad", goal="the human's words")`, take a
style from the top that is not made_recently, and write its id in the brief,
e.g. "announce the new export feature, in social-ad-odd-one-out". The whole
library as one file: https://api.mysiren.ai/api/v1/styles.md.
