brandbrain-flow-orchestrator-mcp
v0.1.0
Published
MCP server for BrandBrain flow orchestration — compose asset flows, run mock/live generation runs, and fetch outputs from any MCP client
Maintainers
Readme
Flow Orchestrator MCP
MCP server for BrandBrain FlowSpec sessions and asset-generation workflow execution.
This server sits on top of the BrandBrain backend flow-session API. It does not talk to model providers directly. The backend remains the source of truth for:
- canonical template bundles
- persisted flow sessions
- validation
- mock and live execution
- run traces and artifacts
What it is for
Use this MCP server when an LLM or operator needs to:
- seed a new asset-generation flow from a canonical template
- build or refine a stored asset canvas workflow on demand
- validate chaining and node structure before generation
- run the workflow in
mockmode first - run a confirmed provider call in
livemode, or restart supported local asset-set processing from a preserved source sheet - fetch a canvas URL that opens the stored workflow in the app
Quickstart
The server is published on npm as brandbrain-flow-orchestrator-mcp and runs over stdio — no global install needed:
npx -y brandbrain-flow-orchestrator-mcpPoint any MCP client at it with the production endpoints:
{
"mcpServers": {
"brandbrain-flow-orchestrator": {
"command": "npx",
"args": ["-y", "brandbrain-flow-orchestrator-mcp"],
"env": {
"BRANDBRAIN_API_URL": "https://api.brandbrain.dev",
"BRANDBRAIN_APP_URL": "https://brandbrain.dev"
}
}
}
}Then call login_brandbrain (browser OAuth) and verify with whoami_brandbrain.
Installer script
From a repo checkout, ./install.sh detects Claude Code, Claude Desktop, and Codex, registers the
server with the production env, and offers to install the bundled agent skill (see
Agent skill):
cd mcp/flow-orchestrator
./install.sh # or ./install.sh --yes to accept every promptSmithery
A smithery.yaml is included for the Smithery registry (stdio, npx
command, config schema for the env vars).
Live runs need a spend ceiling
Live (paid) runs are refused unless the server's environment sets
BRANDBRAIN_MAX_PROVIDER_SPEND_USD_MICROS (USD micros; 1000000 = $1.00) — see
Environment. Mock runs work without it.
Backend contract
The server wraps these backend endpoints:
GET /api/v1/canvas/templatesPOST /api/v1/canvas/sessionsGET /api/v1/canvas/sessions/:sessionIdPUT /api/v1/canvas/sessions/:sessionIdDELETE /api/v1/canvas/sessions/:sessionIdPUT /api/v1/canvas/sessions/:sessionId/asset-set-plans/:planNodeIdPOST /api/v1/canvas/sessions/:sessionId/asset-set-plans/:planNodeId/approveGET /api/v1/canvas/sessions/:sessionId/asset-set-plans/:planNodeId/historyPOST /api/v1/canvas/sessions/:sessionId/validatePOST /api/v1/canvas/sessions/:sessionId/runsGET /api/v1/canvas/sessions/:sessionId/runs/:runIdGET /api/v1/canvas/sessions/:sessionId/runs/:runId/outputsGET /api/v1/canvas/sessions/:sessionId/runs/:runId/artifacts/:artifactIdPOST /api/v1/canvas/sessions/:sessionId/regenerate
In production, the backend must have access to the canonical flow bundles at BRANDBRAIN_FLOW_TEMPLATE_ROOT.
Environment
BRANDBRAIN_API_URL- Backend API base URL.
- Default:
http://127.0.0.1:4444
BRANDBRAIN_APP_URL- Optional app base URL used to generate returned canvas links.
- Default:
https://brandbrain.dev
BRANDBRAIN_FLOW_MCP_BEARER_TOKEN- Optional static bearer token.
- Usually not needed once browser OAuth login is configured.
BRANDBRAIN_MAX_PROVIDER_SPEND_USD_MICROS- The most this server may authorise a single
liverun to spend, in USD micros (1000000= $1.00). The backend caps any one run at100000000($100.00) regardless. - There is no default and no value meaning unlimited. Leave it unset and
every
liverun is refused before any request reaches the backend; onlymockruns work. - This is deliberately process environment rather than a tool argument, so an agent driving this server cannot choose its own spend ceiling. Set it yourself, in your MCP client configuration, to the most you are willing to lose on one run.
- The amount actually authorised is never this number. It is the backend's own conservative estimate for that exact run; this value only decides whether the run is allowed to proceed at all.
- The most this server may authorise a single
See .env.example.
Install and run
From a repo checkout (mcp/flow-orchestrator):
cd mcp/flow-orchestrator
npm install
npm run build
BRANDBRAIN_API_URL=http://127.0.0.1:4444 npm startFor local iteration:
cd mcp/flow-orchestrator
npm install
BRANDBRAIN_API_URL=http://127.0.0.1:4444 npm run devMCP client configuration
Published package (preferred — see Quickstart):
{
"mcpServers": {
"brandbrain-flow-orchestrator": {
"command": "npx",
"args": ["-y", "brandbrain-flow-orchestrator-mcp"],
"env": {
"BRANDBRAIN_API_URL": "https://api.brandbrain.dev",
"BRANDBRAIN_APP_URL": "https://brandbrain.dev"
}
}
}
}Built server from a checkout:
{
"mcpServers": {
"brandbrain-flow-orchestrator": {
"command": "node",
"args": [
"<repo-checkout>/mcp/flow-orchestrator/dist/index.js"
],
"env": {
"BRANDBRAIN_API_URL": "https://api.brandbrain.dev",
"BRANDBRAIN_APP_URL": "https://brandbrain.dev"
}
}
}
}Agent skill
The source of truth for the agent skill that teaches agents how to drive this server safely lives
in this repository at skill/ (SKILL.md + references/ + agents/openai.yaml). It
also ships inside the published npm package, next to this README.
The skill encodes the intended usage pattern for agent sessions:
- list templates first
- create template-backed flows when possible
- validate after edits
- run
mockbeforelive - return
canvas_urlto the user
To install it for Codex, copy the directory into the skills root:
cp -R skill/ ~/.codex/skills/brandbrain-flow-orchestrator/For other agents, copy skill/ into their skills directory (or point them at the packaged copy
inside brandbrain-flow-orchestrator-mcp). ./install.sh offers the Codex copy automatically.
Dev server with tsx:
{
"mcpServers": {
"brandbrain-flow-orchestrator": {
"command": "npx",
"args": [
"tsx",
"<repo-checkout>/mcp/flow-orchestrator/src/index.ts"
],
"env": {
"BRANDBRAIN_API_URL": "http://127.0.0.1:4444",
"BRANDBRAIN_APP_URL": "http://127.0.0.1:3000"
}
}
}
}Tools
| Tool | Purpose | Typical use |
| --- | --- | --- |
| login_brandbrain | Open BrandBrain browser login and store a local MCP session | First step when the backend is protected and no local agent session exists |
| whoami_brandbrain | Inspect the locally stored MCP session | Confirm the active BrandBrain agent identity before editing flows |
| logout_brandbrain | Revoke the stored MCP refresh session and clear local credentials | Explicitly end a local MCP login |
| list_asset_flow_templates | Discover canonical starter flows | First step when the user describes a goal but not a template |
| create_asset_flow | Create a stored flow session from a template, goal, or explicit flowSpec | Start a new workflow |
| get_asset_flow | Fetch the stored session and flow spec | Inspect current graph before editing |
| edit_asset_flow | Replace title/description and optionally replace the stored flowSpec | Structural edits after creation |
| update_asset_set_plan | Atomically update asset-set authoring fields and persist a complete draft manifest proposal | Configure a named asset set before asking the user to approve it |
| approve_asset_set_plan | Approve the exact persisted manifest revision and content hash | Record explicit user approval without forging manifest authority fields |
| get_asset_set_plan_history | Fetch manifest revisions and approval events | Audit the persisted asset-set lifecycle |
| validate_asset_flow | Run backend graph validation | After every material edit and before any run |
| start_asset_flow_run | Start a background mock or live run and return a pollable task envelope | Standard MCP clients that only support ordinary tool calls |
| get_asset_flow_task | Poll a background task started by the MCP server | Check status until completed or failed |
| start_regenerate_asset_flow_node | Start a background regeneration request from a target node | Polling clients; asset-set local targets rerun the set-scoped pipeline from split, while legacy graphs may invoke the full runner |
| run_asset_flow | Execute the session in mock or live mode | Task-backed execution for long-running runs |
| regenerate_asset_flow_node | Start a task-backed regeneration request from a target node | Asset-set local reprocessing or a separately confirmed legacy regeneration whose full reachable scope has been inspected |
| get_asset_flow_run | Inspect a stored run result and traces | Audit what happened after execution |
| get_flow_processor_health | Read normalized local processor readiness and pinned revisions | Verify zero-token local split/background/vector processing is available before relying on it |
| get_flow_outputs | Return every durable output's metadata and app URL, with a bounded inline preview | View/validate generated assets after a run (run the flow first) |
| get_flow_trace | Per-node run trace: status, timing, resolved prompts, provider request highlights, artifacts, errors | Investigate why a run/node failed or what was sent to the provider |
| open_flow | Open the flow's canvas (/crm/canvas/{id}) in your default browser | Jump from MCP to the visual editor |
| open_flow_gallery | Open the flow's run gallery (/crm/canvas/{id}/gallery) in your default browser | Review every run's output images + per-image cost/trace after running |
Asset-set authoring and delivery
Asset sets use a persisted plan and manifest lifecycle. Do not skip or combine the two approval boundaries: the user first approves the proposed manifest, then later confirms the specific paid provider call immediately before live execution.
Use this sequence for a new or changed asset set:
get_asset_flow
-> update_asset_set_plan
-> present the persisted draft and wait for explicit user manifest approval
-> approve_asset_set_plan
-> validate_asset_flow
-> run_asset_flow(mode="mock") or start_asset_flow_run(mode="mock") + polling
-> present the paid-call scope and wait for explicit confirmation
-> run_asset_flow(mode="live") or start_asset_flow_run(mode="live") + polling
-> get_flow_outputs
-> open_flow_galleryThe three plan-lifecycle tools accept camelCase MCP inputs:
| Tool | Required inputs |
| --- | --- |
| update_asset_set_plan | sessionId, planNodeId, positive expectedFlowRevision, non-negative expectedManifestRevision, non-empty label, brief, and desiredPurpose, requestedCount from 1 through 64, requestedFormats exactly ["png"] or ["png", "svg"], and the complete proto-JSON manifest object |
| approve_asset_set_plan | sessionId, planNodeId, positive expectedFlowRevision, positive expectedManifestRevision, and the exact non-empty manifestContentHash returned with the persisted draft |
| get_asset_set_plan_history | sessionId and planNodeId |
update_asset_set_plan is one atomic write: it updates the plan authoring fields and complete
manifest proposal together, recomputes the canonical manifest hash, and persists a new draft. Read
the returned persisted draft, revisions, and content hash; do not calculate or invent approval
fields in the client. Present the named items, layout, required formats, and spend policy to the
user. Only after the user approves that exact proposal should the client call
approve_asset_set_plan using the returned revisions and hash. Stale revision or hash conflicts
must be surfaced to the caller, followed by a fresh get_asset_flow; do not retry with guessed
values. get_asset_set_plan_history is the source of truth for revision and approval records.
The format contract is intentionally narrow:
- Every item must request PNG as both a desired and required format. A transparent PNG is the required master.
- SVG may be the optional second requested format. It is desired and published only after the sanitizer, complexity, and raster-fidelity gates pass.
- Mark SVG required in the manifest only when the user explicitly requires vector delivery and accepts that a failed SVG gate leaves that item requiring review. An optional SVG failure must not erase or fail a valid PNG master.
Generic graph-editing tools cannot author or alter an asset-set plan node. This includes
edit_asset_flow, apply_flow_edits, and the individual incremental edit tools: they cannot add,
remove, or change plan fields, manifest contents, or approval state. Use
update_asset_set_plan for authoring changes and approve_asset_set_plan for approval.
After approval, require validate_asset_flow to return valid=true, then finish a terminal mock
run before considering live execution. Immediately before a provider-backed source-generation run:
- Inspect the persisted graph and selected execution target. Exactly one provider-backed node may be reachable by that live run; if more than one is reachable, stop instead of using regeneration as a workaround. A zero-provider asset-set local reprocessing run is a distinct recovery path described below and must be verified from its returned target and usage evidence.
- Tell the user the operation, provider/model when known, item or set scope, expected size/quality, and estimated or bounded cost when available. Ask for explicit confirmation of that one call.
- After confirmation, start one live run with a new
idempotencyKey. Further provider calls need fresh confirmation.
For asset-set templates configured for local review, the approved source sheet then proceeds through the canonical local split, background removal, trim/normalization, vectorization, review, and output stages. These local post-processing stages consume no model/provider tokens and incur no provider cost; verify that claim from returned usage, trace, or cost evidence. Do not claim an unverified zero-token result. If semantic provider review is enabled, stop before an atomic live run because the current MCP surface cannot separately confirm and dispatch that additional paid stage.
Finish with get_flow_outputs and open_flow_gallery. get_flow_outputs returns the complete
outputs metadata array for the selected run, including a browser-loadable artifact_url for every
durable output. It inlines previews for at most 16 outputs, subject to the per-image byte cap; outputs
beyond that preview limit remain present in metadata with their app URLs. Use those returned URLs
for individual PNG/SVG delivery and the gallery for visual review.
Incremental editing
Edit a stored canvas without resending the whole graph (preferred over edit_asset_flow, which
replaces the entire flowSpec).
Asset-set plan nodes are the exception: generic whole-flow and incremental edits cannot add, remove, or modify them. Follow the dedicated asset-set authoring and delivery lifecycle instead.
Important — the backend validates the WHOLE graph on every save. Each single-op tool below does its own
PUT, so it is rejected (HTTP 400) if it leaves the graph transiently invalid. A generate node needs ≥1 input edge and ≥1 output edge, so you cannot add a bare generate node and wire it up in separate calls. To build or extend a generate chain, useapply_flow_edits, which applies an ordered op list in memory and saves once — only the final graph is validated.
| Tool | Purpose |
| --- | --- |
| apply_flow_edits | Batch: apply an ordered list of ops in one save. Use this to build/extend chains. |
| add_flow_node | Add one node (type = prompt/reference/style/generate/output/frame/note); auto-positions and auto-ids if omitted. Safe alone only for nodes valid while unwired (prompt/style/reference/note/frame). |
| update_flow_node | Patch a node's fields and/or position by nodeId |
| remove_flow_node | Remove a node and any edges referencing it |
| connect_flow_nodes | Add an edge between two existing nodes (prompt\|reference\|style\|output -> generate, or generate -> output) |
| disconnect_flow_nodes | Remove an edge by edgeId, or by sourceNodeId+targetNodeId |
| set_generate_target | Set a generate node's surface/aspectRatio/composition target |
Tool inputs are camelCase (modelId, sourceNodeId); they are emitted to the backend as the
canonical snake_case proto-JSON. Each tool returns the backend validation result.
Example — build a full prompt+style → generate → output chain in one call:
apply_flow_edits {
"sessionId": "flow_session_...",
"ops": [
{ "op": "add_node", "type": "prompt", "id": "bp", "text": "A bold packshot on seamless paper" },
{ "op": "add_node", "type": "style", "id": "bs", "palette": ["#0a0a0a", "#f5f5f0"] },
{ "op": "add_node", "type": "generate", "id": "bg", "provider": "openai", "modelId": "gpt-image-1" },
{ "op": "add_node", "type": "output", "id": "bo" },
{ "op": "connect", "sourceNodeId": "bp", "targetNodeId": "bg" },
{ "op": "connect", "sourceNodeId": "bs", "targetNodeId": "bg" },
{ "op": "connect", "sourceNodeId": "bg", "targetNodeId": "bo" },
{ "op": "set_generate_target", "nodeId": "bg", "surface": "packshot", "aspectRatio": "1:1" }
]
}Task-backed run tools
run_asset_flow and regenerate_asset_flow_node are registered as MCP task tools.
- plain
client.callTool(...)is not the right API for these tools - task-capable clients should use
client.experimental.tasks.callToolStream(...) - this avoids the default short request timeout problem on real image generation
- the MCP task stays
workingwhile the backend run isqueuedorrunning; it does not complete merely because the asynchronous dispatch returned a run ID - backend
completedandpartialruns complete the MCP task; backendfailed,cancelled, andoutcome_unknownruns fail it while retaining the terminal run and error in the stored task result - a requested task TTL that is shorter than the bounded backend polling window is raised to a safe accepted TTL so the SDK task store cannot expire before terminal evidence is persisted
- accepted TTLs are capped at
2,147,000,000ms(just below Node's signed 32-bit timer maximum), including when a client requests a larger value - terminal polling retries only transient read failures (network/timeouts, HTTP 429, and HTTP 5xx) with bounded exponential backoff and jitter inside one overall deadline; authentication, other HTTP 4xx, and unsupported backend statuses fail immediately
- the default poll deadline is mode-aware:
liveruns poll for up to 30 minutes (a ~10-minute asset-set generation fits inside one task), whilemockruns keep the short ~9.5-minute default; a constructor/configuredrunPollTimeoutMsalways overrides the default - each timed-out run read aborts and settles its fetch before retrying, so retries cannot leave overlapping orphan reads
- task-backed and standard tools share one 32-operation execution limit across dispatch and terminal polling; capacity is released when that lifecycle reaches a terminal outcome
Claude Code (and any client without task augmentation): do NOT call
run_asset_flow. It errors immediately withMCP error -32601: Tool run_asset_flow requires task augmentation (taskSupport: 'required'). Use the polling pair below instead —start_asset_flow_run→ pollget_asset_flow_taskuntilstatus: "completed", which returns therun_id. Same forregenerate_asset_flow_node→start_regenerate_asset_flow_node.
Example:
const stream = client.experimental.tasks.callToolStream(
{
name: "run_asset_flow",
arguments: { sessionId, mode: "mock" },
},
CallToolResultSchema,
{ task: { ttl: 600000 } },
);
for await (const message of stream) {
if (message.type === "result") {
console.log(message.result.structuredContent);
}
}Standard polling tools
Some MCP clients do not support the experimental task streaming API and can only issue ordinary
callTool(...) requests. For those clients, the server also exposes a polling pair:
start_asset_flow_runget_asset_flow_task
and the regenerate equivalents:
start_regenerate_asset_flow_nodeget_asset_flow_task
start_* returns immediately with a task payload like:
{
"task": {
"id": "flow_task_...",
"kind": "run",
"status": "working",
"session_id": "flow_session_...",
"mode": "mock",
"poll_interval_ms": 1000
}
}Poll until the task is terminal. queued and running backend runs remain working; completed
and partial map to completed, while failed, cancelled, and outcome_unknown map to failed.
The terminal polling envelope contains the refreshed backend run, including its result/error
evidence. Dispatch happens once; polling only reads the pinned session/run identity and does not
repeat provider work. Transient reads use bounded backoff; non-transient errors and unknown statuses
fail immediately.
Each start_* call creates a distinct dispatch attempt, including keyless calls and repeated calls
with the same explicit idempotency key. This ensures the backend can re-evaluate the latest flow
revision and lineage; canonical idempotency/replay decisions remain backend-owned. If distinct
dispatches return the same immutable session/run ID, the MCP server shares only their terminal GET
FlowRun promise to avoid redundant reads. Each caller independently builds its task result from
that run and its own dispatch envelope, so prepared authority is never borrowed from another caller.
Local processor health
Call get_flow_processor_health before promising no-token local asset processing. It is a read-only,
allowlisted view of /health/processors that reports enabled, available, platform,
execution_class, provider_token_cost, revisions.vtracer, revisions.resvg, and any health
error. Its capabilities object reports split, trim_normalize, vectorize, local_review,
and the background-removal fields deterministic_matte_or_alpha, local_model_fallback, and
production_ready. Background removal is production-ready only when the deterministic path is
present and the configured local BiRefNet fallback passes its provenance-aware health probe. A
runtime without BiRefNet can still preserve valid alpha or remove a known matte, but callers must
not promise general-purpose background removal when production_ready is false. Missing capability
fields from older backends default to false. Unknown backend fields are dropped so credentials and
transport details cannot be exposed. An unavailable optional processor is returned as health
evidence even when the backend health route uses HTTP 503. These checks are local and have no
provider-token cost.
Then poll:
const started = await client.callTool({
name: "start_asset_flow_run",
arguments: { sessionId, mode: "mock" },
});
const taskId = (started.structuredContent as { task?: { id?: string } }).task?.id;
for (;;) {
const polled = await client.callTool({
name: "get_asset_flow_task",
arguments: { taskId },
});
const payload = polled.structuredContent as {
task?: { status?: string };
run?: { id?: string };
};
if (payload.task?.status === "completed" || payload.task?.status === "failed") {
console.log(payload);
break;
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}Notes:
get_asset_flow_taskreturns the finalrunpayload once the background task completes- after completion, use
get_asset_flow_runif you want the persisted run again later - the polling task registry is in-memory to the MCP server process, so task IDs are not durable across MCP restarts
- the shared execution coordinator rejects new work before backend dispatch when its 32-operation capacity is full, regardless of whether work came from a standard or task-backed tool
- capacity is released after dispatch and terminal-poll failures as well as normal completion; a rejected shared poll is removed before the next attempt, so a retry performs a fresh backend read
- the standard registry retains at most 128 tasks, evicts the oldest terminal entries as needed, and expires terminal entries after 10 minutes
- prepared POST requests still carry the expected flow revision and resolved-policy hash, but those values are not exposed as fenced durable identity because the persisted FlowRun does not echo them. Retained dispatch identity contains only matchable fields: session, execution mode, actual target, generated or supplied idempotency key, approved manifest hash, and execution lineage. Durable failed-run evidence is accepted only from the typed error around the actual run or regenerate POST and only when every retained field matches; get-session/prepare/authentication failures never carry run evidence, and legacy fields that were not sent remain absent
- retained failure diagnostics follow the real FlowRun ProtoJSON shape and are explicitly allowlisted
and bounded, with a hard 64 KiB serialized budget across the result diagnostic envelope. Later
entries are deterministically dropped, and
diagnostics_truncatedis set whenever a count limit, per-string bound, or global byte budget clips retained diagnostics. Safe trace/checkpoint/processor/item/artifact identifiers, statuses, issue codes, dimensions, sizes, hashes, MIME types, and durations may be retained; prompts, request/response bodies, provider usage, evidence JSON, credentials, filesystem paths, URLs, provenance, secrets, and unknown fields are discarded
Browser OAuth login
The preferred auth path is browser-based Google OAuth through BrandBrain:
- call
login_brandbrain - the MCP server opens
/login/agenton the BrandBrain app with a localhostagent_callback - BrandBrain redirects to the backend Google OAuth start endpoint
- after Google authentication, the backend redirects back to the local MCP callback with an OTC
- the MCP server exchanges the OTC for agent tokens and stores them locally
Use whoami_brandbrain to confirm the current session and logout_brandbrain to revoke it.
If a flow tool returns Not authenticated. Run login_brandbrain first., do that before retrying.
Recommended workflow
1. Log in when needed
login_brandbrainwhoami_brandbrain
2. Start from templates unless you have a reason not to
Preferred path:
list_asset_flow_templatescreate_asset_flowwith:titlegoalif you want the tool to infer a templatetemplateIdif you know the exact starter
- read back
canvas_url
This keeps the first graph aligned with the checked-in canonical bundles.
goal/descriptiondo NOT propagate into the template's prompt nodes. A template-backed create seeds generic prompt text (e.g.campaign-visual→ "Design a hero campaign visual…"). If you run as-is you get a generic hero image, not your subject. After creating, patch the brief with a singleupdate_flow_node { nodeId: "prompt_brief", text: "<your real subject>" }(stays valid — noapply_flow_editsneeded). The startercampaign-visualgraph wiresprompt_brief → "Context:"andprompt_guardrails → "Brief:"in the assembled provider prompt, and injects the template's style palette/theme — neutralizestyle_primarytoo if the template look fights your subject.
3. Validate structure before spending tokens
Always call validate_asset_flow before run_asset_flow.
Use mock mode first and poll or stream it to a terminal completed result. Inspect the persisted
mock run before continuing. It verifies:
- graph validity
- runnable generates
- downstream output materialization
- stage/regeneration semantics
- stored session integrity
4. Edit only when template inference is not enough
Use edit_asset_flow with a replacement flowSpec when:
- the user needs non-template structure
- you need to add or remove nodes explicitly
- you want to rewrite edges or stages
If you only need a different starting point, create a new flow from a better template instead of over-editing the old one.
5. Confirm each live provider call
Before any provider-backed live run:
- Require the current graph to pass validation and a completed mock run.
- Inspect the persisted graph and selected execution target. Exactly one provider-backed node may be reachable; stop if more than one is reachable because the current runner cannot isolate those calls. If none is reachable, treat the operation as local processing rather than a paid call and verify that from the returned target and usage evidence.
- Disclose the operation, provider/model when known, item or set scope, expected size/quality, estimated or bounded cost when available, and whether a preserved source can avoid a new call.
- Ask for fresh, explicit confirmation immediately before dispatching that one provider call.
A live run additionally requires BRANDBRAIN_MAX_PROVIDER_SPEND_USD_MICROS to be configured on
this server. Without it the run is refused locally and nothing is dispatched. That ceiling is the
user's, not the agent's: it is not a tool argument and cannot be raised from a tool call.
The user's earlier desire for real outputs, manifest approval, or the presence of provider credentials is not authorization to spend. Confirmation is call-specific: every additional source generation, provider-backed review, retry, or regeneration needs its own immediate confirmation.
6. Pick the execution path that matches the client
- use
run_asset_flowandregenerate_asset_flow_nodewhen the client supports MCP task streaming - use
start_asset_flow_runorstart_regenerate_asset_flow_nodeplusget_asset_flow_taskwhen the client only supports ordinary tool calls
7. Regenerate without assuming provider isolation
For an asset-set session, regenerate_asset_flow_node or
start_regenerate_asset_flow_node may target a local process node such as split,
remove-background, trim-normalize, vectorize, or review. The backend anchors this operation
at split and reruns the full set-scoped local pipeline from the latest eligible persisted raw
sheet. It does not expose per-item processing or a caller-selected source run/artifact. After the
run starts, inspect get_asset_flow_run and require the returned target to be local-only and
downstream-only, with its base run/attempt and input checkpoint/artifact lineage, before claiming
that it preserved the source and incurred no provider cost.
On legacy/non-asset-set graphs, regeneration records targetNodeId but may still invoke the full
runner. It does not guarantee a single-node or single-output provider retry. Inspect the complete
reachable provider scope; if any provider-backed work can run, disclose that scope/cost and obtain
fresh explicit confirmation immediately before regeneration. Stop when multiple provider-backed
nodes are reachable.
Example operator flows
Create a new workflow from a goal
Input:
{
"title": "Spring launch system",
"goal": "Build a campaign system with a hero and story adaptation"
}Tool:
create_asset_flowFollow-up:
validate_asset_flowstart_asset_flow_runwithmode=mock- poll
get_asset_flow_taskuntilcompleted - inspect
canvas_url
Task-stream alternative:
validate_asset_flowrun_asset_flowwithmode=mock- inspect
canvas_url
Edit and rerun an existing session
get_asset_flow- modify the returned
flow_spec edit_asset_flowvalidate_asset_flowstart_asset_flow_run- poll
get_asset_flow_task
Restart asset-set local processing from a preserved sheet
{
"sessionId": "flow_session_...",
"targetNodeId": "split",
"mode": "live"
}Tool:
regenerate_asset_flow_nodeFor a standard client, use start_regenerate_asset_flow_node with the same arguments, poll
get_asset_flow_task, then inspect get_asset_flow_run. Do not use this example for a provider
retry: the zero-provider claim depends on the returned asset-set target being set-scoped,
local-only, downstream-only, and anchored to the persisted raw-sheet lineage.
Validation and test commands
cd mcp/flow-orchestrator
npm test
npm run test:integration
npm run buildTroubleshooting
No templates available
The backend cannot see the canonical flow bundle root. Check:
BRANDBRAIN_FLOW_TEMPLATE_ROOT- deployed bundle files under
/opt/brandbrain/evals/flowsin production
400 validation on create or edit
The backend rejected the provided flowSpec. Call validate_asset_flow or inspect the returned validation payload.
401 or 403
Set BRANDBRAIN_FLOW_MCP_BEARER_TOKEN if the backend APIs are protected.
live runs fail
The MCP server is not the model execution layer. Check provider credentials and backend runtime config, not the MCP package. Credentials are only an execution prerequisite; they never replace the fresh user confirmation required immediately before each provider call.
login_brandbrain times out
login_brandbrain blocks until you finish the browser round-trip. MCP clients enforce a request
timeout (the SDK default is 60s), which can abort the call before you sign in. Options:
- complete the Google sign-in promptly, or
- raise your client's MCP request timeout (e.g. Claude Code honours
MCP_TIMEOUT), or - run the standalone login (15-minute window, no MCP layer), which writes the same session file:
node scripts/login.mjs(withBRANDBRAIN_API_URL/BRANDBRAIN_APP_URLset).
Local verification scripts
scripts/ contains a stdio client for exercising the server end to end without a full MCP client:
node scripts/login.mjs # browser OAuth, 15-min window, writes the session file
node scripts/smoke.mjs list # list registered tool names
node scripts/smoke.mjs whoami # check the stored session
node scripts/smoke.mjs templates # list flow templates
node scripts/smoke.mjs create '{"title":"X","templateId":"campaign-system"}'
node scripts/smoke.mjs call '{"name":"apply_flow_edits","arguments":{ ... }}'
node scripts/smoke.mjs runflow '{"sessionId":"flow_session_...","mode":"mock"}'All read BRANDBRAIN_API_URL / BRANDBRAIN_APP_URL from the environment.
