# openFlow (remote · openflowmcp.com)

Omni Flash and Veo video, Nano Banana images on Google Flow, from any MCP client

- Trust score: 83/100 (high trust)
- Change this week: +4
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-20

## Components

- remote · `openflowmcp.com`: 83/100 (this document), [markdown](https://verifymcp.io/servers/molkex-omni-flash-veo3-nano-banana/openflowmcp.md), [page](https://verifymcp.io/servers/molkex-omni-flash-veo3-nano-banana/openflowmcp)

## Channel facts

- Endpoint: `https://openflowmcp.com/mcp`
- Transports: `streamable-http`
- Auth: `required`
- Version: `1.1.0`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-20.

- **Endpoint Security**: 89/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - Authorisation is enforced on tool calls, advertised via RFC 9728 protected-resource metadata. Discovery is public, which costs nothing: no tool can be invoked without a token.
  - HTTPS is enforced; there's no plaintext access path.
  - HSTS check failed: the Strict-Transport-Security header is absent.
  - DNSSEC check failed: this domain isn't protected by DNSSEC.
  - The authorisation server offers only Dynamic Client Registration (RFC 7591), which MCP 2026-07-28 deprecated in favour of Client ID Metadata Documents.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 68/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 11278 tokens (~322/item across 35 items; 35 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 67/100
  - Stability observed for 20 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 67/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 0% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 35 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 36 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the openFlow MCP server?

openFlow is a hosted endpoint at https://openflowmcp.com/mcp, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add --transport http molkex-omni-flash-veo3-nano-banana 'https://openflowmcp.com/mcp'
```

### Cursor

```json
{
  "mcpServers": {
    "molkex-omni-flash-veo3-nano-banana": {
      "url": "https://openflowmcp.com/mcp"
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "molkex-omni-flash-veo3-nano-banana": {
      "type": "http",
      "url": "https://openflowmcp.com/mcp"
    }
  }
}
```

### Codex

```toml
[mcp_servers.molkex-omni-flash-veo3-nano-banana]
url = "https://openflowmcp.com/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "molkex-omni-flash-veo3-nano-banana": {
      "type": "remote",
      "url": "https://openflowmcp.com/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add molkex-omni-flash-veo3-nano-banana --url 'https://openflowmcp.com/mcp' --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  molkex-omni-flash-veo3-nano-banana:
    url: "https://openflowmcp.com/mcp"
```

### Netclaw

```json
{
  "McpServers": {
    "molkex-omni-flash-veo3-nano-banana": {
      "Transport": "http",
      "Url": "https://openflowmcp.com/mcp"
    }
  }
}
```

### Vellum

```bash
assistant mcp add molkex-omni-flash-veo3-nano-banana -t streamable-http -u 'https://openflowmcp.com/mcp'
```

### Other

```json
{
  "mcpServers": {
    "molkex-omni-flash-veo3-nano-banana": {
      "type": "http",
      "url": "https://openflowmcp.com/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-20 (score 83, +1)

No change was recorded against any check on this day. Stability & Change Management went from 63 to 67. That category is still filling its 30-day observation window: 19 days of observed history at the previous scan, 20 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-18 (score 82, +1)

- [security] The server rewrote its instructions, which are the text every model session reads
- [security] Tool “check_credits” rewrote its description, which is the text the model reads
- [security] Tool “create_character_from_description” rewrote its description, which is the text the model reads
- [security] Tool “create_project” rewrote its description, which is the text the model reads
- [security] Tool “edit_video” rewrote its description, which is the text the model reads
- [security] Tool “extend_video” rewrote its description, which is the text the model reads
- [security] Tool “flow_dashboard” rewrote its description, which is the text the model reads
- [security] Tool “generate_character_expression_sheet” rewrote its description, which is the text the model reads
- [security] Tool “generate_character_turnaround” rewrote its description, which is the text the model reads
- [security] Tool “update_character” rewrote its description, which is the text the model reads
- [security] Tool “upscale_image” rewrote its description, which is the text the model reads
- [security] Tool “upscale_video” rewrote its description, which is the text the model reads

### 2026-09-17 (score 81, +1)

- [security] Tool “add_account_token” rewrote its description, which is the text the model reads

### 2026-09-14 (score 80, +1)

No change was recorded against any check on this day. Stability & Change Management went from 43 to 47. That category is still filling its 30-day observation window: 13 days of observed history at the previous scan, 14 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-12 (score 79, +1)

No change was recorded against any check on this day. Stability & Change Management went from 37 to 40. That category is still filling its 30-day observation window: 11 days of observed history at the previous scan, 12 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-10 (score 78, +1)

No change was recorded against any check on this day. Stability & Change Management went from 30 to 33. That category is still filling its 30-day observation window: 9 days of observed history at the previous scan, 10 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-08 (score 77, +1)

No change was recorded against any check on this day. Stability & Change Management went from 23 to 27. That category is still filling its 30-day observation window: 7 days of observed history at the previous scan, 8 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-06 (score 76, +1)

No change was recorded against any check on this day. Stability & Change Management went from 17 to 20. That category is still filling its 30-day observation window: 5 days of observed history at the previous scan, 6 at this one. The score rises as the window fills, whether or not the server changes.

## MCP tools (35)

### `generate_with_face` (~401 tokens)

Generate a NEW image of the SAME PERSON/character as an existing photo or
    portrait — dress them differently, move them somewhere else, make an avatar. This
    is the tool for "here is my photo, now put me in a suit / in a park", and also
    the ONLY tool for "generate another picture of <character I already made>".

    Pass exactly ONE of:
      character: the name you gave a character via create_character_from_description
      or create_character_from_photo(..., name=...). Looked up in your own list —
      see list_characters(). This is the normal way once a character has a name.
      photo_media_id: a mediaId directly (create_character_from_photo's
      `portrait_media_id`, or any earlier generate_image media_id) — for a one-off
      face you haven't registered as a named character.
    prompt: describe the NEW scene/outfit and say it is the same person, e.g.
    "the same man, now in a navy suit in a bright office".

    Why this and not entity_id/character_slot_index: a character's Flow CARD is not
    what carries resemblance into a NEW scene — the reference image itself does
    (verified live, repeatedly). Combining entity_id with an image reference in the
    same call also fails outright (HTTP 500). So this always generates from the
    reference image alone, never from entity_id — that is deliberate, not a
    workaround to fix later.

    project_id: put the result into a project made with create_project instead of the
    account's default one. photo_media_id itself stays account-bound, as always.

Input parameters:

- `aspect` (string)
- `character`
- `include_preview` (boolean)
- `model` (string)
- `photo_media_id`
- `project_id`
- `prompt` (string, required)

### `generate_image` (~964 tokens)

Generate an image from a text prompt via Google Flow.

    model — pass ONE of these exact ids (from GET v1/flow/models, verified 2026-07-25):
      NARWHAL      = "Nano Banana 2"      — the DEFAULT, ~30s
      GEM_PIX_2    = "Nano Banana Pro"    — highest quality, ~40s, has a separate DAILY
                                            per-account quota (429 rotates accounts)
      HARBOR_SEAL  = "Nano Banana 2 Lite" — ~40s
    Anything else is rejected with HTTP 400 — there is no silent fallback, so a wrong id
    wastes a round trip, not credits. Do NOT guess names like "nano_banana_pro": that is
    the model FAMILY key from the config, not the id the generate call takes.

    image_inputs: mediaIds from prior generations, edit-in-place (keeps them pixel-exact).
    control_images: base64 image bytes (no data: prefix) of a locally rendered layout/control
    image — uploaded first, then prepended to image_inputs so the generator follows an exact
    spatial layout instead of prose. For whole-scene layout control prefer generate_scene.

    seed: 1..900000, fixes the generation so the same request returns the same image. Omit
    for a random seed. Needed to compare two prompts, two models or two control images
    honestly — without it every run is a fresh roll and the difference you are measuring
    drowns in noise. Measured 2026-08-09: with an UNCHANGED control image a fixed seed
    reproduces the frame to ~0.1%; once the control image changes, the seed no longer keeps
    the character — for that use image_inputs with a reference frame's mediaId.

    project_id: put the result into a project made with create_project instead of the
    account's default one — one project per end client keeps their work separate. Omit it
    and nothing changes from before.

    include_preview: ON by default since 2026-08-21 — a generated image nobody can see
    in the chat is half a result. Pass False to get the plain dict back and nothing else
    (worth doing in a loop o…

Input parameters:

- `aspect` (string)
- `control_images`
- `image_inputs`
- `include_preview` (boolean)
- `model` (string)
- `project_id`
- `prompt` (string, required)
- `seed`

### `music_generate` (~367 tokens)

Generate a full song from a text brief via Google Flow Music (Lyria).

    prompt — free text: genre, mood, BPM, instruments, and whether you want vocals or an
    instrumental (e.g. "dreamy synthwave, retro 80s, 110 bpm, instrumental"). The Producer
    agent turns it into a finished track (usually ~2-3 minutes, and it returns TWO variants).

    model — "lyria" (Lyria 3.5, the default flagship) or "lyria-pro" (Lyria 3 Pro, legacy).
    lyrics — optional explicit words to sing; omit for the model to write its own / stay
    instrumental.

    SAVE THE RESULT. Each returned clip carries a public `audio_url` (a Google Cloud Storage
    .m4a, no auth header needed) plus `download_path` on our server. Unless the user said
    otherwise, download the audio into their project. Fields per clip: clip_id, title,
    duration (seconds), lyrics, audio_url, wav_url, image_id (cover, feeds music_video),
    image_url. Returns {"clips": [...], "media": [...], "conversation_id": "..."} when done,
    or a job_id to poll with check_job if it isn't ready within ~90s.

    conversation_id — pass the one from an earlier result to keep working on the same track
    ("same song but 30 seconds shorter"); the Producer remembers what it just made. Omit it
    for a fresh start. Every result carries one, so a follow-up is always possible.

Input parameters:

- `conversation_id`
- `lyrics`
- `model` (string)
- `prompt` (string, required)

### `music_chat` (~304 tokens)

Talk to the Google Flow Music Producer agent instead of one-shotting a track.

    Use this when the work is a conversation rather than a single brief: asking what it can
    do, having it write lyrics first, or editing a track it already made ("drop the vocals",
    "same song, 30 seconds shorter", "make the chorus bigger"). For a plain "make me a song
    about X", music_generate is the shorter path.

    message — what to say. conversation_id — omit on the first message, then pass the one
    that comes back so the Producer keeps its memory of the track. model — same choices as
    music_generate.

    Returns {"conversation_id", "text", "suggested_actions", "clips": [...], "media": [...]}.
    `text` is the Producer's reply. `suggested_actions` are the follow-ups it offers — show
    them to the user, they are good prompts for the next turn. `clips` is EMPTY when the
    Producer only talked, and filled when it decided to actually make audio; treat a filled
    `clips` exactly like a music_generate result and save the audio.

    A reply is usually seconds, but a turn that produces a song takes minutes and comes back
    as a job_id to poll with check_job.

Input parameters:

- `conversation_id`
- `message` (string, required)
- `model` (string)

### `music_stems` (~132 tokens)

Split a track we generated into stems (vocals, instrumental, drums, bass, …).

    clip_id — the id of a clip returned by music_generate (or music_video's song). Runs on
    the same account that made the track. Returns {"stems": {name: "/v1/media/..."}, "media":
    [...]} when done, or a job_id to poll with check_job. Each stem is a downloadable .m4a on
    our server (public download_path). Save the ones you need into the user's project.

Input parameters:

- `clip_id` (string, required)

### `music_list` (~21 tokens)

List the user's music tracks.

Input parameters:

- `limit` (integer)

### `music_get` (~24 tokens)

Get metadata for a single music track.

Input parameters:

- `clip_id` (string, required)

### `music_download` (~31 tokens)

Download the raw .m4a file of a track to the server.

Input parameters:

- `clip_id` (string, required)

### `music_credits` (~18 tokens)

Check remaining flowmusic credits and subscription tier.

### `generate_scene` (~809 tokens)

Build a location precisely from a scene-layout/v2 map and generate its plate.

    This is the "exact location building" path: instead of describing the room in prose
    (which the model reinterprets — furniture drifts, depths change between shots), it
    renders a control image from the map's boxes (each object a colored block at its true
    position/size/depth, plus a 1-meter floor grid in true perspective), then asks the
    generator to REPLACE each block with its real object in the same camera — so the plate
    follows the geometry pixel-wise. Same room from another camera_id is the same space.

    scene_layout: a scene-layout/v2 dict — {format, units, room{w,d,h}, objects{...boxes},
      cameras{<id>:{pos,look_at,fov_deg,framing?}}, ...}. The camera must carry fov_deg.
    camera_id: which camera in scene_layout.cameras to render from.
    aspect: PORTRAIT (9:16), LANDSCAPE (16:9) or SQUARE. 9:16 and 16:9 from one camera share
      the vertical FOV, so depths are identical across aspects.
    style / extra: prepended / appended prose (art style, mood) — geometry comes from the map.

    A box may carry `rot: [rx, ry, rz]` (degrees around the world X/Y/Z axes) and `pivot:
    [x, y, z]`, applied as p' = Rx·Ry·Rz·(p − pivot) + pivot, pivot defaulting to the box
    centre. That is how a POSE is expressed — a raised arm is a box rotated about the
    shoulder. Measured live 2026-08-09: moving a hand by 2–20 cm is ignored by the
    generator, rotating the arm by 15–60° comes through. Build motion out of angles.

    seed / image_inputs: same meaning as in generate_image. For a SEQUENCE of frames of one
    character, generate a reference frame first, then pass its mediaId as image_inputs on
    every following frame — that is what keeps the face, clothes and colours identical.
    A fixed seed alone does not: it only repeats an unchanged control image.

    depth: render the guide as a plain greyscale depth map instead of the colour hybrid
      (for an e…

Input parameters:

- `aspect` (string)
- `background` (string)
- `calibration` (boolean)
- `camera_id` (string, required)
- `control_png` (boolean)
- `depth` (boolean)
- `extra` (string)
- `image_inputs`
- `project_id`
- `scene_layout` (object, required)
- `seed`
- `style` (string)

### `generate_video` (~383 tokens)

Generate a video from a text prompt via Google Flow (Veo models).

    video_model_key options: abra_t2v_4s (default, 7 credits), veo_3_1_t2v_lite
    (10cr, visibly cleaner), veo_3_1_t2v (100cr, "Quality" tier), veo_3_1_t2v_fast (20cr).

    project_id: put the clip into a project made with create_project instead of the
    account's default one — one project per end client keeps their work separate.

    Video generation routinely takes 1-3+ minutes, so this almost always returns
    a job_id rather than a finished result — call check_job with it, waiting a
    few seconds between checks, until status is 'done' or 'failed'.
    beats: how many DISTINCT action phases the prompt describes — "ears flick back",
    "tail lashes", "strikes the ball", "ball flies off" are four. Give it and the server
    picks the clip length for you (about two phases per second) and says in the reply what
    it picked and why. Measured 31.08.2026: the model STRETCHES whatever you describe over
    whatever length you ask for, so one phase in four seconds comes out as slow motion, and
    the same text at six seconds instead of four loses 15% of its movement. Adjectives are
    not phases: "springy cartoon motion" adds nothing. Leave beats out and the length you
    passed is used unchanged.

Input parameters:

- `aspect` (string)
- `beats`
- `include_preview` (boolean)
- `project_id`
- `prompt` (string, required)
- `video_model_key` (string)

### `edit_video` (~168 tokens)

Rewrite a video this service already generated, from a plain description:
    "make the sky stormy", "take the passer-by out of the shot", "warmer light".

    media_id: the id from a previous generate_video* result. Only our own videos can be
    edited — the id belongs to a Google account's project, so the job is pinned to that
    account. 20 credits per edit (abra_edit is the only model Flow offers here).

    This edits the WHOLE clip. It is not a mask tool: describe the change, not the region.

Input parameters:

- `aspect` (string)
- `include_preview` (boolean)
- `media_id` (string, required)
- `prompt` (string, required)
- `video_model_key` (string)

### `extend_video` (~261 tokens)

Continue a video this service already generated — Flow writes what happens next.

    media_id: the id from a previous generate_video* result (the bare id, not the
    /v1/media/... path). Only videos made through this service can be extended: the id
    belongs to a specific Google account's project, so the job is pinned to that account
    instead of rotating. An id we never produced comes back as a clear error, not a
    mystery failure.

    Use this for shots that need to run longer than one generation, or to keep a scene
    going without re-describing it: the continuation inherits the source clip's world,
    which a fresh text-to-video call cannot do.

    Default model veo_3_1_extension_lite: 10 credits, adds 8 seconds.

    project_id: which project the continuation lands in. The job is pinned to the source
    clip's account regardless, so pass the project the source was generated in (or omit it).

Input parameters:

- `aspect` (string)
- `include_preview` (boolean)
- `media_id` (string, required)
- `project_id`
- `prompt` (string, required)
- `video_model_key` (string)

### `generate_video_between_frames` (~536 tokens)

Generate the transition between TWO given frames: the video starts at the first
    image and ends at the second one, Flow invents everything in between.

    This is Flow's "Кадры" with both slots filled — a different mechanism from
    generate_video_from_image, which only pins the FIRST frame and lets the ending drift.
    Use this when the ending matters: a logo that must resolve exactly, a character that
    must land in a specific pose, two stills from a storyboard that need connecting.

    Both images are raw base64 bytes, no data: URI prefix. The default model
    (veo_3_1_interpolation_lite, 10 credits, 8 seconds) is the cheap one — transitions
    are something you retry a few times, and defaulting to the 100-credit key would make
    every experiment expensive.

    beats: how many DISTINCT action phases the prompt describes — "ears flick back",
    "tail lashes", "strikes the ball", "ball flies off" are four. Give it and the server
    picks the clip length for you (about two phases per second) and says in the reply what
    it picked and why. Measured 31.08.2026: the model STRETCHES whatever you describe over
    whatever length you ask for, so one phase in four seconds comes out as slow motion, and
    the same text at six seconds instead of four loses 15% of its movement. Adjectives are
    not phases: "springy cartoon motion" adds nothing. Leave beats out and the length you
    passed is used unchanged.

    project_id: put the clip into a project made with create_project instead of the
    account's default one.
    beats: how many DISTINCT action phases the prompt describes. The server then picks
    the clip length itself (about two phases per second) and says so in the reply. For a
    first+last-frame transition keep it low: the two frames already fix the endpoints, and
    asking for more seconds than the action needs only stretches it — measured 31.08.2026,
    the same text at six seconds instead of four lost 15% of its movement.

Input parameters:

- `aspect` (string)
- `beats`
- `end_image_base64` (string, required)
- `include_preview` (boolean)
- `project_id`
- `prompt` (string, required)
- `start_image_base64` (string, required)
- `video_model_key` (string)

### `generate_video_from_image` (~361 tokens)

Generate a video that starts from a given image (i2v — image-to-video).

    start_image_base64: raw base64-encoded image bytes, no data: URI prefix.
    Mutually exclusive with reference-image (r2v) generation.

    For "make content that looks like this real photo of a person" specifically,
    prefer generate_video_with_reference (r2v) over this — see its docstring for why.
    i2v is better suited to "continue visually from this exact frame" (e.g. chaining
    clips), not identity preservation across a whole new scene.

    beats: how many DISTINCT action phases the prompt describes — "ears flick back",
    "tail lashes", "strikes the ball", "ball flies off" are four. Give it and the server
    picks the clip length for you (about two phases per second) and says in the reply what
    it picked and why. Measured 31.08.2026: the model STRETCHES whatever you describe over
    whatever length you ask for, so one phase in four seconds comes out as slow motion, and
    the same text at six seconds instead of four loses 15% of its movement. Adjectives are
    not phases. Leave beats out and the length you passed is used unchanged.

    project_id: put the clip into a project made with create_project instead of the
    account's default one.

Input parameters:

- `aspect` (string)
- `beats`
- `include_preview` (boolean)
- `project_id`
- `prompt` (string, required)
- `start_image_base64` (string, required)
- `video_model_key` (string)

### `generate_video_with_reference` (~322 tokens)

Generate a video using up to 3 reference images (r2v — e.g. a consistent
    character/subject across the video). reference_images_base64: raw base64-encoded
    image bytes (no data: URI prefix), up to 3. Mutually exclusive with start-image
    (i2v) generation.

    THIS IS THE RECOMMENDED WAY to get content that actually looks like a real person
    from a real photo — confirmed live 2026-07-11: r2v conditions generation on the
    reference photo's actual pixels and reliably preserves the real face, unlike the
    character system's `generate_character_image`/`generate_character_turnaround`
    (those condition on a TEXT description + a loosely-attached reference and were
    confirmed to drift to an unrelated-looking person). For a still IMAGE rather than
    video, use this and read `first_frame_path` off the result once done (via
    check_job) — same-day testing found no reliable image-only equivalent: a plain
    generate_image call with image_inputs pointed at an external photo was confirmed
    to silently ignore the reference entirely.

    project_id: put the clip into a project made with create_project instead of the
    account's default one.

Input parameters:

- `aspect` (string)
- `include_preview` (boolean)
- `project_id`
- `prompt` (string, required)
- `reference_images_base64` (array, required)
- `video_model_key` (string)

### `generate_character_image` (~513 tokens)

Generate a portrait or body image for an existing Flow character.

    entity_id: the character's entityId — from create_character or
    create_character_from_photo, or create_character_from_description's own result
    (which already calls this for slot 0 — no need to call it again for that slot).
    character_slot_index: 0 for portrait, 1 for body. Flow keeps the two slots
    visually consistent server-side once they share an entityId — no reference
    image needed for the body shot.

    ONE-SHOT PER SLOT, confirmed live 2026-07-25: each slot can be written exactly
    once — a second generate_character_image call into an ALREADY-FILLED slot fails
    (HTTP 500), it does not overwrite. Also confirmed: the FIRST write into a slot
    500s on a card with no personality_notes saved yet — call update_character with
    personality_notes before the first generate_character_image on a fresh entity_id.
    If you're using create_character_from_description, both of these are already
    handled for slot 0; this caveat mainly matters if you're driving slot 1 (body) or
    entity_id yourself.

    THIS TOOL DOES NOT PLACE A CHARACTER INTO A NEW SCENE — it only ever (re)writes
    the character's own portrait/body slot, once. For "generate a picture of this
    character doing X", use generate_with_face(character=..., prompt=...) instead,
    which conditions on the reference image, not entity_id.

    CAVEAT confirmed live 2026-07-11: for a character seeded from a REAL PHOTO
    (create_character_from_photo), this text-prompt generation is NOT reliable for
    identity — it conditions on a text description, not the real photo's pixels, and
    was confirmed to drift to an unrelated-looking person even with a detailed
    physical description saved on the entity. Fine for invented/stylized characters.
    If the goal is "content that looks like a real person," use
    generate_video_with_reference (r2v) instead — see its docstring.

    Only works for the account whose…

Input parameters:

- `aspect` (string)
- `character_slot_index` (integer, required)
- `entity_id` (string, required)
- `model` (string)
- `prompt` (string, required)

### `create_character` (~213 tokens)

Create a new, empty Flow character (just gets you an entity_id — no portrait/body,
    no description yet). Plain HTTP, no browser involved.

    Low-level building block — for "make me a character from a description and generate
    its portrait", call create_character_from_description instead of this; it does this
    step plus the two below in the right order for you.

    Next steps after you have entity_id, IN THIS ORDER (reversing them 500s on the first
    portrait write, confirmed live 2026-07-25): 1) update_character with
    personality_notes (and display_name) — the slot generation below needs a saved
    description to succeed at all; 2) generate_character_image with
    character_slot_index=0 (portrait), then =1 (body) — each slot accepts exactly ONE
    write, a second call into an already-filled slot 500s rather than overwriting.

Input parameters:

- `account` (string, required)

### `create_character_from_photo` (~785 tokens)

Create a new Flow character whose portrait (and optionally body) is a REAL
    PHOTO, not a text description or generation. Both photos become the character's
    slot-0 (portrait) and slot-1 (body) images directly — Flow's own upload mechanism
    in its New Character composer, discovered live 2026-07-11 — no image generation
    call happens for either slot, so resemblance is exact, not "similar style". Boots
    the on-demand Chrome for this account (~15-30s, longer if body_photo_base64 is
    also given), same as create_character, no Android/recaptcha involved.

    photo_base64: raw base64-encoded image bytes, no data: URI prefix — portrait.
    body_photo_base64: same format, optional — ideally a full-body photo of the same
    person, for a guaranteed-exact body image (no generation at all for that slot).

    physical_description: IMPORTANT, look at the photo yourself and write this —
    a detailed physical description (build, hair color/style, eye color, clothing,
    distinguishing features), similar in spirit to: "Adult male, mid-20s, ~180cm,
    lean build. Medium-brown wavy hair, light blue-grey eyes, faint stubble. Wears
    a dark grey hoodie, dark jeans, white sneakers." This gets saved into the same
    "Character Info" field as Flow's own UI. CORRECTED same day as this tool was
    first built: an earlier version of this doc claimed body/multi-view generation
    can never stay consistent with a photo-seeded portrait — WRONG, confirmed live.
    The actual missing ingredient was this detailed text spec — with it, both
    generate_character_turnaround and generate_character_expression_sheet below
    produce genuinely consistent, on-model results (reproduced live against Flow's
    real UI, not just guessed). Skip this only for a bare portrait-only character
    with no further generation planned.

    Next step after you have entity_id: update_character for a display name, then
    generate_character_turnaround / generate_character_expression_sh…

Input parameters:

- `account` (string, required)
- `body_photo_base64`
- `name`
- `photo_base64` (string, required)
- `physical_description`

### `create_character_from_description` (~356 tokens)

Invent a new character from a text description and generate its portrait — the
    full "make me a character, no photo involved" path in ONE call, instead of gluing
    together create_character + update_character + generate_character_image yourself.

    name: short and unique among YOUR OWN characters (see list_characters()) — how
    you'll refer to this character afterwards, e.g. character="detective_marlowe" in
    generate_with_face.
    description: physical description — build, hair, eyes, clothing, distinguishing
    features. Saved on the character's card AND used to generate the portrait, so
    write it as concretely as you would any image prompt.
    account: Google account (and its project) to create the character under.

    The portrait is written to Flow's card exactly ONCE — that slot cannot be
    regenerated. Calling this again with the SAME name does not retry it: it fails
    fast with a clear message instead of hitting a raw HTTP 500 downstream. Want a
    different look? Use a new name.

    Returns {"name", "entity_id", "portrait_media_id", "portrait_url", "hint"} once
    done. Next step for ANY further image of this character: generate_with_face(
    character=name, prompt="...") — never entity_id/character_slot_index, which only
    works for that one portrait write and cannot place the character into new scenes.

Input parameters:

- `account` (string, required)
- `aspect` (string)
- `description` (string, required)
- `display_name`
- `model` (string)
- `name` (string, required)
- `portrait_prompt`

### `list_characters` (~156 tokens)

List the characters YOU created (create_character_from_description, or
    create_character_from_photo with name=...) and can now generate more images of
    via generate_with_face(character=<name>, prompt=...). Read-only, no Flow calls,
    no credits spent.

    Returns {"characters": [{"name", "source", "description", "portrait_media_id",
    "entity_id", "project", "account", "created_at"}, ...]}, oldest first.
    `source` is "description" (create_character_from_description) or "photo"
    (create_character_from_photo). `portrait_media_id` is what generate_with_face
    actually uses — a character with it set to null has no usable reference yet.

### `generate_character_turnaround` (~553 tokens)

Generate a single image showing the character's FULL BODY from 3 angles
    (front/side/back) in one row, labeled — a "turnaround sheet". Pure HTTP as of
    2026-07-26 — no browser involved (see below for what changed and what that does
    and does NOT prove).

    History, so the mechanism change doesn't erase the reason this tool exists:
    confirmed live 2026-07-11 that the plain Android-bearer generate_character_image
    path produces an INCONSISTENT result for this exact kind of multi-view prompt,
    even with a detailed physical description already saved on the entity — a real
    web composer (Chrome/CDP) driving Flow's own translation/agent layer was reliably
    consistent for the same prompt. That is why this tool exists as a separate path
    from plain image generation, and that finding still stands.

    What changed 2026-07-26: this no longer drives that browser composer. The
    underlying job type (generate_character_scene) moved to a THIRD mechanism, not
    either of the two compared above — plain HTTP with image_inputs=[the character's
    own portrait_media_id from the registry], the same face-preserving mechanism
    generate_with_face uses. This was done to remove the last browser dependency
    (worker.py's BROWSER_JOB_TYPES is now empty), not because this new path's
    multi-view consistency was re-verified — it has NOT been checked live yet whether
    image_inputs alone holds up as well as the old composer did for a 3-angle sheet.
    Treat multi-view reliability here as unverified-but-plausible until confirmed by
    eye against real output, not as re-proven.

    Still recommend a detailed physical description saved first (create_character_from_photo's
    physical_description param, or update_character's personality_notes) — that
    recommendation carries over from the 2026-07-11 finding above; whether it still
    matters mechanically on this new image_inputs path (vs. the composer's own
    entityContext-driven translation laye…

Input parameters:

- `account` (string, required)
- `character_slot_index` (integer)
- `entity_id` (string, required)
- `outfit_description` (string)

### `generate_character_expression_sheet` (~340 tokens)

Generate a single image showing the character's FACE with several different
    expressions in one row, labeled — an "expression sheet". Same job type and same
    history as generate_character_turnaround — see its docstring for the full story:
    the 2026-07-11 finding (plain Android-bearer image path is inconsistent for
    multi-view prompts, the real web composer was reliably consistent) is why this
    tool exists, but as of 2026-07-26 it no longer drives that composer — pure HTTP
    with image_inputs=[portrait_media_id] instead (no browser). Multi-view consistency
    on THIS new path has not been re-verified live yet; the recommendation to keep a
    detailed physical description saved on the entity carries over from the old
    finding but hasn't been separately re-tested against the new mechanism either.

    account: Google account that owns this entity_id's project.
    expressions: list of expression labels, e.g. ["нейтральное выражение",
    "удивление", "лёгкая улыбка", "смех"] (default if omitted). Keep to 3-5 for a
    readable single-row layout.
    character_slot_index: which slot to write into (0 = portrait, default — this
    overwrites the existing portrait image with the expression sheet, matching how
    this was used when reproducing the feature; 1 = body slot also works).

Input parameters:

- `account` (string, required)
- `character_slot_index` (integer)
- `entity_id` (string, required)
- `expressions`

### `create_project` (~301 tokens)

Create a NEW Flow project on one of your Google accounts and return its project_id.

    Use this to give every one of YOUR OWN clients their own namespace: create a project
    per client once, then pass that project_id to generate_image / generate_video so
    their results never mix with anyone else's.

    account — which Google account creates it (see flow_dashboard / check_credits for the
    emails). The project is created under that account, but a project id is a label every
    account accepts: generation keeps rotating accounts as before, so results stay grouped
    by project_id, not by account. What is still account-bound is media_ids — a follow-up
    that reuses one (image_inputs edit-in-place, extend_video, upscale) only works on the
    account that produced that particular frame, exactly as today.
    name — optional human-readable title, e.g. the client's name.

    The returned project is usable for generation IMMEDIATELY. It may not show up in the
    Flow web UI's project list (`registered: false` in the result says so) — that only
    affects browsing in Google's own interface, not this API: every result comes back as
    urls/media_ids in the generation response either way.

    Returns {"project_id": ..., "account": ..., "registered": true|false}.

Input parameters:

- `account` (string, required)
- `name`

### `check_credits` (~68 tokens)

Check projects for a Google account (or all). NOTE: for the credit BALANCE and a
    full at-a-glance overview, prefer `flow_dashboard()` — it returns real credit
    numbers per account + totals. This tool only returns the project list.

Input parameters:

- `account`

### `flow_dashboard` (~143 tokens)

The account-pool control panel: for EVERY Google account, the real credit
    balance, subscription tier, project count, and health status, plus totals — one
    call, formatted for reading. Use this to answer "how many accounts, how many
    credits on each and in total, what projects" — the operator's dashboard.

    Returns a `summary` string (human-readable table) plus structured `accounts` and
    `totals`. Credit numbers are live (Flow's /v1/credits). Credit REFRESH date is not
    exposed by Flow's API, so it's shown as an estimate note, not a hard date.

### `update_character` (~84 tokens)

Save a character's display name and/or personality notes. Without this, a
    character stays 'Untitled Character' with no description even after portrait/body
    images exist. account must be whichever Google account's project owns this entity_id.

Input parameters:

- `account` (string, required)
- `display_name`
- `entity_id` (string, required)
- `personality_notes`

### `upscale_image` (~286 tokens)

Upscale an existing generated image (from a prior generate_image call) to a
    higher resolution -- the same "1K/2K/4K" choice Flow offers when downloading.

    account: the Google account whose project owns media_id. Do not guess it and do not
    ask the user: the generate_image result you already have carries it in the
    `account_used` field -- pass that value verbatim. A media_id only exists inside the
    project of the account that produced it, so any other account returns a clean error.

    target_resolution: "2K" or "4K". Not every image supports upscaling (depends on the
    source model/size) and 4K may be locked behind a paid plan tier on some accounts --
    both cases come back as a clean error rather than a crash, so just report it if that
    happens instead of retrying.

    Takes roughly 15-30s -- returns a job_id to poll with check_job if it is not done
    within ~35s.

    Returns {"media_id": <new upscaled mediaId>, "download_path": "/v1/media/....jpg",
    "resolution": ...} when done.

Input parameters:

- `account` (string, required)
- `media_id` (string, required)
- `target_resolution` (string)

### `upscale_video` (~296 tokens)

Upscale an existing generated video (from a prior generate_video call) to a
    higher resolution -- the same "270p/720p/1080p/4K" choice Flow offers when downloading.

    Needs an active subscription, like any other video operation, and spends the account's
    own Flow credits.

    account: the Google account whose project owns media_id. Do not guess it and do not
    ask the user: the generate_video result you already have carries it in the
    `account_used` field -- pass that value verbatim. A media_id only exists inside the
    project of the account that produced it, so any other account returns a clean error.

    target_resolution: "1080p" or "4K". 4K may be locked behind a paid plan tier on some
    accounts -- comes back as a clean error rather than a crash, just report it.

    Slower than generation: it is kicked off and then polled like a normal video
    generation -- returns a job_id to poll with check_job if it is not done within ~40s.

    Returns {"media_id": <new upscaled mediaId>, "download_path": "/v1/media/....mp4",
    "resolution": ...} when done.

Input parameters:

- `account` (string, required)
- `media_id` (string, required)
- `target_resolution` (string)

### `check_job` (~287 tokens)

Check the status/result of a job previously returned by generate_image,
    generate_video, generate_video_from_image, or generate_video_with_reference.

    Besides status/result it reports PROGRESS, so you can tell the person something
    truthful instead of "still waiting":
      queue_position — how many jobs are ahead (1 = next to be picked up); only while queued
      queue_seconds  — how long it sat in the queue
      run_seconds    — how long the generation itself has been running
      account_used   — which Google account it ran on, once finished
      hint           — the same thing in one human sentence
    A job that is queued behind others needs patience; one that has been running far longer
    than its model's typical time (see list_models) is the one worth reporting as stuck.

    Once status is 'done', SAVE THE FILES: the response carries `download_urls` (no auth
    header needed) and `save_as` (filenames). Write them into the user's project — e.g.
    ./media/<save_as> — and tell them where. The URLs expire within the hour, and the
    server's own copy can be reclaimed when its disk fills; the copy in their project is
    the one that lasts.

Input parameters:

- `include_preview` (boolean)
- `job_id` (string, required)

### `fetch_media` (~486 tokens)

Download a generated media file (video, frame) and return its bytes,
    base64-encoded, so the file can be saved or shown even though the media endpoint
    isn't publicly reachable with your MCP key.

    THIS IS THE EXPENSIVE PATH — prefer `download_url` from the job result. Bytes returned
    here travel as TEXT, and text is where context dies: a 480 KB audio stem is ~640 thousand
    base64 characters, roughly 160 000 tokens, in ONE tool result. The same file behind
    `download_url` costs about twenty. An inline image preview is cheap by comparison (it
    travels as an image, ~450 tokens) — it is base64 in a text field that hurts.

    Because of that this tool REFUSES big files by default and hands back a download link
    instead. Pass force_bytes=True only when you genuinely need the bytes inside the
    conversation and know what it costs.

    path: the `download_path` / `first_frame_path` value from a job
    result (e.g. "/v1/media/<id>.mp4"), or just the bare filename.
    max_kb: size ceiling for an in-band answer (default 64 KB ≈ 21 000 tokens).

    Returns {"filename", "media_type", "base64"}. The download is scoped to your own
    account group — you can only fetch media your key produced. Note videos can be
    several MB, so the base64 payload is large; fetch only when you actually need the
    bytes (to save/display), not to check status (use check_job for that).

    On storage: generated files are NOT deleted on a timer — there is no retention window
    to race. Space is only ever reclaimed when the server's disk runs low, and then only
    from files that were already downloaded through this call; a result nobody fetched is
    never removed to make room. So fetching once and saving the bytes locally is the way to
    keep something for good, and a file you never fetched stays available. A file that was
    reclaimed answers 410 with the date, not a bare 404.

Input parameters:

- `force_bytes` (boolean)
- `max_kb` (integer)
- `path` (string, required)

### `add_account_token` (~415 tokens)

Attach one of YOUR OWN Google accounts to the pool using an oauth_token captured
    on the USER'S OWN machine (password/2FA never leave it). This is the self-serve
    onboarding path — no server-side browser, no SSH.

    HOW TO GET oauth_token — RUN THIS YOURSELF in the user's terminal, do NOT send them
    to download anything. The command is a single line and needs no install beyond Node 22+:

        curl -sL https://openflowmcp.com/onboard_local.js | node

    Windows PowerShell:

        irm https://openflowmcp.com/onboard_local.js -OutFile o.js; node o.js

    It opens the user's own Chrome at accounts.google.com/EmbeddedSetup. Tell them to log
    in and press Accept, then to leave the window alone — the page will hang on a spinner,
    which is normal. The script prints ONE JSON line: {"status":200,"oauth_token":...,
    "email":...}. Pass oauth_token and email here.

    If it prints "need_email": true, the account address could not be read off the page —
    ask the user which Google account they just signed into and pass that as `email`, or
    re-run with ONBOARD_EMAIL=them@gmail.com in front of the command.

    The oauth_token is SINGLE-USE: if this call fails, the user must log in again for a
    FRESH token — never retry the same one.

    On success the account is bound to YOUR key's owner group and the worker picks it up
    within ~30s (no restart). Adding an account is always allowed — even for a key that
    has already spent its free quota, since that is a prerequisite, not a reward.

    Only onboard accounts YOU control — a master token grants full account access.

Input parameters:

- `email` (string, required)
- `oauth_token` (string, required)
- `reset_day` (integer)

### `flow_status` (~246 tokens)

What this key still needs before it can do everything — READ-ONLY, spends nothing.

    Call this BEFORE the first generation of a session, whenever a call is refused, and
    whenever the user asks why something does not work. It answers in one shot: is a
    Google account connected, how much of the free trial is left, is the key linked to a
    GitHub identity, is video paid for.

    The important field is `next_step`: it is the single most blocking thing right now,
    with ready-made wording (`text` in Russian, `text_en` in English) and a `url` to open.
    When it is null, nothing is missing.

    DO NOT keep this to yourself. If `next_step` is set, say it to the user in their own
    language and give them the URL — a step like linking GitHub happens in THEIR browser
    and cannot be done by you. `support` is the operator's Telegram, the service's only
    human channel; there is no email on file for a self-registered key, so nobody will
    reach out to the user first.

### `flow_health` (~59 tokens)

Check whether the Flow generation backend is reachable and see current queue
    depth (queued + warming + running jobs). Note: the Android emulator is now OFF —
    generation runs on a pure-HTTP token path, so there's no warm-up delay anymore.

### `list_models` (~347 tokens)

List every valid model id this service accepts — READ-ONLY, spends no credits,
    calls nothing outside this service. Call this BEFORE generate_image/generate_video
    if you're unsure of an exact id: guessing (e.g. the family key "nano_banana_pro" or
    a display name like "Nano Banana 2" instead of the id "NARWHAL") gets a hard HTTP
    400 on a call that already committed to a task, not a helpful list.

    Returns:
      images: {id: {label, typical_seconds, notes}} for the three valid `model` values
        (generate_image, generate_character_image).
      videos: {id: {label, credits, duration_s, mode, orientation}} for the curated
        `video_model_key` values this server's tools accept via their Literal type —
        mode is one of t2v/i2v/r2v (which generate_* tool it belongs to). `credits` or
        `duration_s` is null where Google's docs never gave that number — a null does
        NOT mean free/instant, it means unconfirmed.
      defaults: the id each tool falls back to when you omit the parameter — unchanged
        from before this tool existed, so already-working callers aren't affected.

    Note: service/api.py (the HTTP layer these tools call through) additionally
    accepts a wider set of rarer, exotic video_model_key values not listed here
    (confirmed-real but uncommon r2v/i2v variants) — this tool only lists the ones
    actually worth choosing from, to stay a short, readable list.

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/molkex-omni-flash-veo3-nano-banana/openflowmcp#diagnostics

## Score history

- 2026-09-20: 83
- 2026-09-19: 82
- 2026-09-18: 82
- 2026-09-17: 81
- 2026-09-16: 80
- 2026-09-15: 80
- 2026-09-14: 80
- 2026-09-13: 79
- 2026-09-12: 79
- 2026-09-11: 78
- 2026-09-10: 78
- 2026-09-09: 77
- 2026-09-08: 77
- 2026-09-07: 76
- 2026-09-06: 76
- 2026-09-05: 75
- 2026-09-04: 75
- 2026-09-03: 74
- 2026-09-02: 74
- 2026-09-01: 73
- 2026-08-31: 73
- 2026-08-30: 36
- 2026-08-29: 36
- 2026-08-28: 36
- 2026-08-27: 36
- 2026-08-26: 36
- 2026-08-25: 36
- 2026-08-24: 36
- 2026-08-23: 36

## Common questions

### What is the openFlow MCP server?

openFlow is an MCP server listed in the public MCP registry as io.github.molkex/omni-flash-veo3-nano-banana. Omni Flash and Veo video, Nano Banana images on Google Flow, from any MCP client. This page covers its hosted endpoint (https://openflowmcp.com/mcp).

### Is the openFlow MCP server safe to use?

openFlow scores 83 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the openFlow MCP server expose?

openFlow exposes 35 tools: generate_with_face, generate_image, music_generate, music_chat, music_stems, and 30 more. Their descriptions and schemas cost roughly 11,036 tokens of context every time the server is loaded.

### Does the openFlow MCP server require authentication?

Yes. openFlow asked us for credentials when we connected, so you will need to authorise it in your MCP client before it can do anything.

### Is the openFlow MCP server still maintained?

openFlow is still listed as active in the MCP registry. We last reached this channel on 20 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- Remote endpoint: https://openflowmcp.com/mcp
- Authorisation metadata: https://openflowmcp.com/.well-known/oauth-protected-resource/mcp
- Repository: https://github.com/molkex/mcp-flow-google
- Website: https://openflowmcp.com/
- Changelog RSS feed: https://verifymcp.io/servers/molkex-omni-flash-veo3-nano-banana/openflowmcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/molkex-omni-flash-veo3-nano-banana/openflowmcp.json
- HTML version of this page: https://verifymcp.io/servers/molkex-omni-flash-veo3-nano-banana/openflowmcp
