Model Context Protocol (MCP) Server for gflow-cli¶
This document describes the design, configuration, security model, and developer setup for the gflow-cli MCP server.
1. Architecture¶
The gflow-cli MCP server acts as a type-safe JSON-RPC interface, supporting two transport mechanisms:
1. stdio Subprocess Transport (gflow mcp run): Runs over standard input/output (stdio), ideal for direct integration with local desktop agents like Claude Desktop, Cursor, or VS Code.
2. SSE HTTP Transport (gflow serve): Runs as a background web daemon over HTTP and Server-Sent Events (SSE), ideal for decoupled web UI dashboards, concurrent scripts, or external clients.
┌────────────────────────────────────────────────────────────┐
│ Client AI Agent (Claude / Cursor / Web Dashboard) │
└─────────────┬───────────────────▲──────────────────────────┘
│ JSON-RPC │ JSON-RPC
│ (stdio / HTTP) │ (stdout / SSE)
┌─────────────▼───────────────────┴──────────────────────────┐
│ MCP Server Adapter (FastMCP / FastAPI app) │
│ Exposes: Tools, Prompts, Resources │
└─────────────┬──────────────────────────────────────────────┘
│ internal calls
▼
┌────────────────────────────────────────────────────────────┐
│ gflow-cli Core │
│ - FlowApiClient (Playwright / REST requests) │
│ - SQLite operations catalog (DataStore) │
├──────────────────────────────┬─────────────────────────────┤
│ Chromium Profile Lock │ Direct SQLite Read │
│ (asyncio & file-based) │ (Fast read paths) │
└─────────────┬────────────────┴──────────────┬──────────────┘
│ writes cookies │ queries history
▼ ▼
[profile_<name>/] [gflow.db]
Adapters side-by-side¶
Both Click CLI (src/gflow_cli/cli.py) and MCP Server (src/gflow_cli/mcp/) are thin adapter layers that drive the core application services (FlowApiClient and repository.py), ensuring zero duplication of business logic.
2. Tools, Prompts, and Resources¶
The server registers three protocol surfaces:
Tools (Executable actions)¶
gflow_generate_image(prompt, model, aspect, count, seed, reference_images, tools, profile, project, instructions): Triggers text-to-image / image-to-image (Imagen / Nano Banana).instructionsis an optional list of ephemeral agent-instruction strings (agentic cohort only).reference_imagesswitches to i2i and accepts either a local file path or a generated image's Flow media UUID. A UUID reference is attached by selecting the already-existing asset in Flow's reference picker — no duplicate copy is uploaded (locating the tile by the media id in its thumbnail URL, and searching the recorded display name to surface it when needed); gflow falls back to uploading the asset's on-disk local file only when it can't be located in place (e.g. it lives in a different project's picker).projectgenerates into an existing Flow project id (mirrors CLI--project) — pass the reference's project to keep it selectable in place.gflow_generate_video(prompt, mode, aspect, initial_frame, end_frame, reference_images, model, duration, count, tools, profile, project): Triggers vertical or landscape video generation (Veo).modeist2v/i2v/r2v;model(veo_lite/veo_fast/veo_quality/omni_flash, aliases accepted),duration(seconds), andcountmirror the CLIgflow videoflags — an omittedmodellets the transport apply its i2v veo-lite default (issue #125), andomni_flashis rejected for i2v-with-frames;i2vrequiresinitial_frame,r2vrequiresreference_images;projectgenerates into an existing Flow project id (mirrors CLI--project).initial_frame,end_frame, andreference_imageseach accept either a local file path or the Flow image UUID of a generated asset — pass a generated image's id straight in to chain image→video, and gflow attaches it for you (the UUID is resolved to the asset's already-on-disk local file and uploaded as the frame; no manual download/re-upload step). A UUID that isn't in your local asset catalog is rejected up front with a clear "Reference Not Found" error; a catalogued asset whose local file is no longer on disk gives a "Reference Not On Disk" error (re-generate it or pass a local path). Note the CLI's semantics diverge here (#287):gflow video i2v --initial-frame <UUID>selects the asset in the project's media picker with no upload and needs no local catalog entry — so the same UUID input can succeed on one surface and fail on the other.gflow_list_projects(profile, limit): Queries SQLite catalog for recent generation folders.gflow_list_characters(profile): Lists Flow Character entities (requires an active browser session).gflow_list_tools(): Lists the prompt tools (name/title/description/category) accepted by the generate tools'toolsparam.gflow_instructions_list(project, profile): Lists a project's persistent Agent-Mode instruction cards (live server brief; credits-free).gflow_instructions_add(project, title, text, refs, enabled, profile): Adds a persistent instruction card. Each ref is classified automatically — local image path → uploaded image reference, asset UUID → image reference, anything else → character id/name (mirrors CLIinstructions add --ref).gflow_instructions_set_enabled(project, enabled, title|card_id, profile): Enables/disables one card selected by title or stable card id (covers CLIinstructions enable/disable).gflow_instructions_rm(project, title|card_id, profile): Removes one card from the brief.gflow_instructions_toggle_mode(project, enabled, profile): Flips the brief-level master switch; cards are left untouched.gflow_instructions_apply(project, cards, profile): Declarative full-sync — REPLACES all cards with the given set (destructive; same entry shape as the CLIinstructions applyfile).
CLI↔MCP parity is enforced programmatically: tests/mcp/test_cli_parity.py walks every CLI leaf command and fails when one has neither a mapped MCP tool nor an explicit exemption with a stated reason. Note the deliberate asymmetry: gflow_generate_video has no instructions param (unlike gflow_generate_image) because the video pipeline (GenerateVideoRequest / the worker) has no instructions support — agentic-video is a typed divergence, and a dead parameter would be silently dropped.
Prompts (Orchestration templates)¶
expand_prompt: Helps the agent structure simple ideas into Google's official 5-component prompt formula (Subject + Action + Location + Composition + Style) before sending them to the generation tools.create_character: Assists agents in defining face, body, and voice parameters for consistent subject generation.
Resources (Context feeds)¶
gflow://docs/mcp-guide: A specialized, agent-targeted guide instructing the LLM to use the registered MCP tools (rather than running raw shell wrapper commands).gflow://docs/known-issues: Feeds critical reCAPTCHA, cookie expirations, and anti-bot mitigation details.gflow://db/schema: Exposes SQLite schema definitions, allowing agents to understand project and media tables.
3. The Dilemma: Why we need the MCP Server (vs. Pure Skill)¶
We analyzed whether a terminal-driven CLI guided by a text skill (e.g., skills/gflow-cli/SKILL.md) is sufficient. The LLM Council concluded that the MCP server is fully justified for these reasons:
| Dimension | Direct CLI via Terminal Execution | Native MCP Server Daemon |
|---|---|---|
| Output Fragility | Ephemeral stdout/stderr log outputs are fragile to format adjustments, progress bars, and ANSI colors. |
Strictly structured JSON payloads containing explicit metadata and absolute output file URIs. |
| Process Lifecycle | High process startup overhead (Python import latency) on every individual execution. | Warm daemon process. Retains active caching of database metadata. |
| Concurrency | A second concurrent process against the same profile is rejected immediately by the cross-process ProfileLease with ProfileLockedError (exit code 11) — a clean fail-fast, never a crash, never a wait. Different profiles run fully in parallel. |
Same ProfileLease enforcement applies to the daemon's requests — a same-profile call made while another is in flight (from the CLI or the MCP daemon) is rejected immediately with ProfileLockedError rather than queued or crashed; different profiles still run fully in parallel. |
| Error Handling | Agent must scan logs for strings or parse exit codes to check status. | Strongly-typed JSON-RPC errors with mapped codes and clear remediation. |
| Transport Safety | Volatile console printing. | Stdout is strictly isolated for JSON-RPC; all logs and warnings route to stderr. |
Error envelope¶
MCP tool failures return a structured error object built from the same RFC 9457
Problem Details as the CLI, plus a retryable boolean. retryable: true
marks a transient failure a scheduler can re-run without operator intervention —
WAF/reCAPTCHA bounce (WafRejectionError), rate-limit (RateLimitError),
transport timeout (TransportTimeoutError), network blip (NetworkError), a
dropped browser session (BrowserSessionClosedError), a Flow web-app crash
(FlowAppError), and an agentic-cohort flap (FlowAgentUiError). Everything
else (auth, content-policy, configuration, security) is terminal
(retryable: false): retrying the identical request fails the same way. This
flag is the same shared classification the CLI --json payload and the
worker-queue error record use (errors.is_retryable) — the three surfaces
cannot drift. On a captured failure the envelope also carries a remote-safe
incident object ({id, capture_status} only — never a local path); see
DEBUGGING § Automatic incident bundles.
4. Setup Instructions¶
Claude Desktop Integration¶
Run the configuration helper command in your terminal:
This automatically appends the server entry to your Claude Desktop configuration file: * Windows:%APPDATA%\Castano\Claude\claude_desktop_config.json
* macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Manual Configuration¶
Depending on how you installed gflow-cli, add one of the following configuration blocks under the mcpServers key of your claude_desktop_config.json:
Option A: Global Installation (Recommended)¶
Use this if you installed gflow-cli globally (e.g. via uv tool install gflow-cli or pip install gflow-cli):
Option B: Local Clone (Development)¶
Use this if you cloned the repository locally and run it via uv:
{
"mcpServers": {
"gflow-cli": {
"command": "uv",
"args": [
"--directory",
"C:/development/github/gflow-cli",
"run",
"gflow",
"mcp",
"run"
]
}
}
}
Cursor Setup¶
- Open Cursor Settings -> Features -> MCP.
- Click + Add New MCP Server.
- Configure depending on your installation:
- Global Installation:
- Name:
gflow-cli - Type:
command - Command:
gflow mcp run
- Name:
- Local Clone (Development):
- Name:
gflow-cli - Type:
command - Command:
uv --directory C:/development/github/gflow-cli run gflow mcp run
- Name:
SSE Daemon Setup (gflow serve)¶
For decoupled clients, local web interfaces, or multi-process frontends, you can run the daemon as an HTTP/SSE service:
This serves the MCP server over Server-Sent Events under FastMCP's standard paths: * Connection endpoint (SSE stream):http://127.0.0.1:8000/sse
* Command posting endpoint: http://127.0.0.1:8000/messages/
Non-loopback binds (e.g. --host 0.0.0.0) require GFLOW_DAEMON_TOKEN to be set.
Note: the background
FlowWorkerqueue manager and the REST/api/v1surface are built as internal foundation but are not yet wired intogflow serve— it currently runs the MCP/SSE server only. See the CHANGELOG for the roadmap.
5. Security & Anti-Bot Mitigations¶
Because the MCP server runs locally, inheriting the host user's permissions and access to their authenticated browser cookies, the following security constraints are enforced:
- Pre-flight Auth Validation: Before launching Chromium/Playwright (which would hang on interactive stdin prompts if cookies are expired), the server checks session validity. If unauthenticated, it returns:
"Authentication required. Run 'gflow auth login' in your local terminal." - Channel Isolation: All internal
structlogconfigurations are forced to write tosys.stderr. The standard output stream (sys.stdout) is globally captured and redirected tosys.stderrfor any unexpected prints, preserving the integrity of the stdio JSON-RPC pipe. - Windows Stdio Encoding: During startup, stdio streams are explicitly reconfigured: This prevents crashes caused by non-ASCII prompt strings on Windows.
- Local Rate-Limiting: Enforces a token-bucket rate limiter with a capacity of 8 tokens and a refill rate of 1 token every 20 seconds (allowing burst filmmaking tasks without timeouts). It also evaluates cumulative session and daily limits (
GFLOW_CLI_SESSION_CREDIT_LIMITandGFLOW_CLI_DAILY_BUDGET) against SQLite logs, failing fast to prevent credit depletion attacks. - CLI-MCP Parameter Symmetry: Automated checks in the CI test suite (
tests/mcp/test_server.py) compare CLI Click parameters with registered MCP tool signatures, ensuring that any added options or flags in the CLI are instantly mirrored in the MCP layer to prevent schema drift.