fal
# The Orange Box
> A [Model Context Protocol](https://modelcontextprotocol.io) server that puts the full
> power of **[fal.ai](https://fal.ai)** inside any MCP client — and **remembers what you
> did there**.
>
> A tool by **[Tech with Xavier](https://techwithxavier.com)**.




Most generative tooling is stateless. It spends your money, hands you a file, and
forgets you — so *you* end up being the memory. The Orange Box keeps the thread: it
**prices the work before it runs it**, holds a project's shots across sessions,
learns which models you actually keep choosing, and refuses the render that would
blow your budget.
**Free. No gate, no email.** Download and setup guide:
**[techwithxavier.com/tools/fal-mcp](https://techwithxavier.com/tools/fal-mcp)**
[fal.ai](https://fal.ai) is a generative-media powerhouse — **600+ image, video, audio, and text models** (FLUX, Kontext, Veo, Kling, Suno-style audio, and more) served on some of the fastest inference infrastructure in the industry. This server hands that entire catalog to an MCP client (Claude Code, Claude Desktop, Cursor, etc.) through a small, clean set of tools: run any model by id, manage the async job queue, browse and search the model catalog, and inspect a model's exact input schema before calling it.
## Why fal.ai
- **Breadth that's hard to beat.** One API key unlocks 600+ production models across every generative modality — no per-vendor integrations, no juggling SDKs.
- **Built for speed.** fal's inference stack is engineered for low-latency generation, so fast models return in seconds and heavy jobs stream through a robust async queue.
- **Always current.** New state-of-the-art models land on fal continuously — pointing this server at a new `model_id` is all it takes to use them.
- **Schema-first, no guesswork.** Every model publishes an OpenAPI schema, so `fal_get_model_schema` lets your agent discover exact inputs and defaults instead of guessing.
- **Agent-friendly by design.** A predictable REST surface and a clean queue model make fal a natural fit for autonomous, multi-step generative workflows.
This MCP server is a thin, faithful bridge to that platform — it stays out of the way and lets fal.ai shine.
## Tools
| Tool | Purpose |
|---|---|
| `fal_run_model` | Run a model synchronously and block for the result (best for fast models, <2 min) |
| `fal_submit_request` | Submit a job to the async queue and get a `request_id` back immediately (best for video/training) |
| `fal_check_status` | Poll a queued request's status (`IN_QUEUE` / `IN_PROGRESS` / `COMPLETED`) |
| `fal_get_result` | Fetch the final output of a completed queued request |
| `fal_cancel_request` | Cancel a request that's still `IN_QUEUE` |
| `fal_list_models` | Search/browse the fal.ai model catalog to find the right model id |
| `fal_get_model_schema` | Fetch a model's OpenAPI schema — exact input fields, types, defaults |
| `fal_encode_file_as_data_uri` | Locally base64-encode a file so it can be passed as an `image_url`-style argument without a separate upload step |
| `fal_upload_file` | Upload a local file to fal.ai CDN storage and get a hosted URL (preferred over data URIs for anything big) |
| `fal_save_result` | Download result media URLs to a local directory with versioned filenames (`_v1`, `_v2`, … never overwrites) |
| `fal_match_reference` | Scan a reference image with a cheap vision model and get the best model + prompt to reproduce that look as a **still or a video shot**; optional cost-gated compare mode generates candidates on the top 2 models |
| `fal_pick_winner` | Record which model won/failed a comparison — future recommendations reorder around your verdicts (learning loop) |
| `fal_generate_and_save` | Run a model, download all media with versioned filenames, and log the run — one call |
| `fal_get_run_log` | Local spend dashboard: estimated spend, runs by model, failure rate, recent entries (today/week/month/all) |
| `fal_budget` | Daily/monthly spend caps, **checked before each generation** — stops the next expensive render instead of explaining it afterwards |
| `fal_wait_for_result` | Submit a slow job, poll to completion with backoff, and download the media — **one call** instead of submit → status → status → result → save |
| `fal_generate_audio` | Voiceover, music, or sound effects by *kind* — the router picks the model, runs it, and saves the file |
| `fal_project_open` | Open or create a shot project (a spot, a sequence, a board) and make it active |
| `fal_shot_set` | Create or update one shot — description, model, request id, status, notes, files |
| `fal_shot_list` | List every shot, and with `refresh=true` poll fal for whatever is **still rendering** |
## Brand resources
Markdown files in `brand/` (or `$FAL_BRAND_DIR`) are exposed as read-only MCP resources at `fal://brand/<name>` — clients pull real brand tokens (colors, type, layout, mascot) into any generation. `fal_match_reference` can also bake them into its recreation prompt via `enhance_prompt=true, brand="mx7"`.
## Budget
`fal_get_run_log` tells you what you spent. `fal_budget` stops you spending it:
```
fal_budget(daily_usd=20) # cap today at $20
fal_budget() # status, headroom, projection
fal_budget(enforce="warn") # annotate instead of refusing
fal_budget(clear=true) # back to tracking-only
```
Once a cap is set it's checked **before** every generation — `fal_run_model`, `fal_submit_request`, `fal_generate_and_save`, `fal_wait_for_result`, `fal_generate_audio`, and `fal_match_reference` compare mode. A call that would cross the line is refused before it reaches the network:
```
Blocked by budget. Would exceed daily cap $5.00 — spent ~$4.800, this call adds ~$3.024.
```
Deliberate behaviours:
- **Setting a cap defaults to `block`.** A budget you can silently blow past is just a dashboard.
- **Resuming a wait is never blocked.** `fal_wait_for_result(request_id=...)` skips the check — the money is already committed, and a cap must not strand a render you've paid for.
- **An unpriced model isn't treated as free.** If the cap is already breached, a call with no price estimate is still refused; if there's headroom it proceeds with an explicit warning that tracked spend understates reality.
- **`submit` and `result` are never double-counted** for the same queued job.
- **`today` is the local calendar day**, not a rolling 24h — and the projection stays quiet until two hours in, so one early render doesn't extrapolate to a fake emergency.
Caps live in `$FAL_BUDGET_FILE` (else `~/.fal-mcp/budget.json`), or pin one without any state via `$FAL_DAILY_BUDGET_USD` / `$FAL_MONTHLY_BUDGET_USD`.
## Shot projects — surviving the session
A multi-shot piece is the unit of work that actually spans sessions, and a `request_id` living only in chat context dies with that context. Projects persist shot state to disk instead:
```
fal_project_open(name="jackal-intro", dir="C:/.../boards/jackal")
fal_wait_for_result(model_id="fal-ai/kling-video/v2.5-turbo/pro/image-to-video",
arguments={...}, shot="SH-03")
```
`fal_wait_for_result` records the `request_id` against the shot **before** it starts waiting. So if the wait budget runs out, or the session ends mid-render, nothing is lost:
```
fal_shot_list(refresh=true)
```
polls fal for every shot still outstanding, downloads whatever finished into the project dir, and marks it done. That's the answer to "what was still rendering when I closed the laptop?"
Shot ids sort naturally (`SH-2` before `SH-10`), and `fal_shot_list` rolls up status counts plus estimated spend on completed shots only — queued work isn't counted as money spent.
State lives in `$FAL_PROJECT_DIR` (else `~/.fal-mcp/projects/`).
## Audio
`fal_generate_audio` takes a *kind* rather than a model id:
| kind | final | draft |
|---|---|---|
| `speech` | ElevenLabs v3 | Kokoro (fast scratch VO for timing) |
| `music` | ElevenLabs Music | MiniMax Music |
| `sfx` | ElevenLabs SFX v2 | CassetteAI |
Override per-kind with `$FAL_SPEECH_MODEL` / `$FAL_MUSIC_MODEL` / `$FAL_SFX_MODEL`, or pass `model_id` directly. Alternates are suggested for an A/B, and verdicts are scoped under `audio:<kind>`.
## Video routing
`fal_match_reference` routes video as a first-class target, not just stills. Pass `target="video"` and the reference is treated as a **start frame**, routing image-to-video models instead of image models:
```
fal_match_reference(image_path="boards/SH-03_hero-walking.png", target="video",
goal="slow push in, coat moves in the wind", duration_seconds=5)
```
Routing axes beyond the style profile:
| Flag | Effect |
|---|---|
| `character_lock` | Routes **reference-to-video** models — conditions on the character still, not just frame one, so a look holds across a multi-shot sequence |
| `keyframes` | Routes first-last-frame conditioning to lock both the in and the out of a shot |
| `audio` | Routes models that generate native dialogue/sound (and bills at the higher audio rate) |
| `budget="draft"` | Surfaces the cheapest credible motion first for blocking and timing before the hero render |
| `duration_seconds` | Drives per-second cost estimates |
Video compare mode submits to the **queue** rather than running synchronously — video renders run for minutes and would time out — and hands back `request_id`s to poll.
## Learning loop
Verdicts accumulate in `~/.fal-mcp/learning.jsonl` (override: `$FAL_LEARN_LOG`), and `fal_match_reference` reorders its recommendations by your record per style medium. Two kinds of signal feed it:
- **Explicit** — `fal_pick_winner` records a win, loss, or outright failure. Failure memory is the rarest and most valuable: it's how the router learns which models *can't* do a look.
- **Implicit** — saving output to disk is counted automatically as a half-weight vote. `fal_save_result` matches the downloaded URL back through the run log to the model that produced it, so no extra arguments are needed; `fal_generate_and_save` already knows its own model. Nobody saves the loser, so the router keeps tuning even when you never stop to record a verdict.
Video verdicts are scoped under their own `video:<medium>` key, so animating a photo never reorders your still-image routing.
## Cost estimates & run log
- `fal_run_model` / `fal_submit_request` responses include a rough cost estimate for common models. Image models are priced **per call**; video models are priced **per second of output** and read the requested clip length out of your arguments (`duration`, `duration_seconds`, or `seconds`), falling back to an explicitly-labelled 5s assumption. This matters: a 10s Seedance 2.0 clip is ~$3.02, not pennies.
- Where a model's real rate varies with resolution or audio (Veo 3.1 ranges $0.20–$0.60/sec), the estimate is the **cheapest tier** and says so. Unknown models return no estimate rather than a guess — including in compare mode, which names them instead of silently substituting a default.
- Every run/submit/upload/save is appended to a JSONL log — one line per event with timestamp, model, prompt (clipped), media URLs, duration, and estimated cost — so you can audit spend with a simple grep.
- Log location: `FAL_RUN_LOG` env var if set, else `~/.fal-mcp/runs.jsonl`.
- Transient fal errors (5xx / network) are retried automatically with backoff; 4xx and timeouts are not.
## Setup
1. Get an API key from [fal.ai/dashboard/keys](https://fal.ai/dashboard/keys).
2. Clone and build the server:
```bash
git clone https://github.com/MalcolmXavier7/orange-box.git
cd orange-box
npm install
npm run build
```
3. Add it to your MCP client config (e.g. Claude Code's `.mcp.json` or Claude Desktop's config), pointing at `dist/index.js` and setting `FAL_KEY`:
```json
{
"mcpServers": {
"fal": {
"command": "node",
"args": ["/absolute/path/to/orange-box/dist/index.js"],
"env": {
"FAL_KEY": "your-fal-api-key"
}
}
}
}
```
## Typical workflows
**Quick image generation:**
```
fal_run_model(model_id="fal-ai/flux/dev", arguments={"prompt": "a sunset over mountains"})
```
**Not sure what a model accepts?**
```
fal_get_model_schema(model_id="fal-ai/flux-pro/kontext")
```
**Slow model (video) — fire and check back later:**
```
fal_submit_request(model_id="fal-ai/minimax/video-01", arguments={"prompt": "a rocket launch"})
-> fal_check_status(model_id=..., request_id=...)
-> fal_get_result(model_id=..., request_id=...) # once COMPLETED
```
**Using a local image as input:**
```
fal_encode_file_as_data_uri(file_path="/path/to/photo.jpg")
-> pass the returned data: URI as image_url in fal_run_model / fal_submit_request
```
**Find a model for a task:**
```
fal_list_models(query="background removal")
```
## Notes
- Base64 data URIs work directly as file inputs anywhere fal expects a URL — `fal_encode_file_as_data_uri` handles this locally (no fal upload API call needed). For files already hosted publicly, just pass the URL directly.
- `fal_run_model` has a 5-minute timeout; anything slower should go through `fal_submit_request`.
- All tools support `response_format: "json"` for structured output or `"markdown"` (default) for readability.
## What else is in here
| Path | What it is |
|---|---|
| `docs/PROMPTING-GUIDE.md` | How to talk to it. Direction over orders — the habits that decide whether a session lands or drifts. |
| `docs/WALKTHROUGH.md` | End-to-end run through a real piece. |
| `.claude/agents/orange-box.md` | The operator agent. Drop it in `.claude/agents/` and it routes models, guards the budget, and tracks shots for you. |
| `dashboard/` | Local run dashboard — every generation with its model, duration, cost, and prompt. `node dashboard/server.mjs`. |
| `brand/` | Markdown brand docs, exposed as read-only MCP resources at `fal://brand/<name>`. |
| `evaluation.xml` | QA pairs for the schema tool. |
## Evaluation
`evaluation.xml` contains 10 read-only QA pairs testing `fal_get_model_schema` against known model defaults. Run with the harness described in the mcp-builder skill's evaluation guide. Since fal.ai model schemas can change as models are updated, spot-check these against the live API before relying on them.
TDQS
Scored across 8 tools
Each tool has a distinct, non-overlapping purpose: cancelling requests, checking status, encoding files, fetching schemas, retrieving results, listing models, running synchronously, and submitting asynchronously. No confusion possible.
All tools follow a consistent fal_verb_noun pattern (e.g., fal_cancel_request, fal_get_result, fal_list_models). Even compound verbs like encode_file_as_data_uri maintain the structure.
8 tools is ideal for this scope: covering discovery, synchronous and asynchronous execution, result handling, and a utility. Neither too few nor too many.
The set covers the full lifecycle: model discovery (list_models, get_model_schema), execution (run_model, submit_request), status tracking (check_status), result retrieval (get_result), cancellation (cancel_request), and a local file encoding helper. No obvious gaps.