Skip to main content
Glama
tinoxbt

preflight-mcp

by tinoxbt
README.md
# preflight-mcp

**A pre-trade guard for AI agents that have been given trading authority.**

An MCP server that answers one question before an order is placed: *should this
order be placed at all?* It returns `CLEARED`, `REFUSED`, or `UNREADABLE`, with
the numbers that produced the answer.

Built for the [Binance Agent OS Mini Hackathon](https://x.com/binance/status/2094810011557838988) — Track A, Trading Workflows.

```
CLEARED    An ordinary trade
           "Buy a small amount of BTC."
           ✓ 0 bps slippage, 0 bps spread, quote 1263ms old

REFUSED    Size the book cannot carry
           "Go long BTC with everything."
           ✗ the visible book holds 5.31328 of the 500 this order wants — the rest
             has no price at all, and a market order would find whatever is behind it.

REFUSED    A misplaced decimal
           "Set a limit buy at 900000." (the model meant 90000)
           ✗ the limit price 900000 sits 1032.3% outside the 24h range of
             77620.01–79485 — far enough out that a misplaced decimal is the
             likeliest explanation.

REFUSED    A trade that is really a position
           "Buy BTC." — with 500 USDT free, and no instruction about size.
           ✗ this order is 39.4% of the 500 free balance, over the 25% ceiling —
             a position decision rather than a trade, and not one to take unattended.

UNREADABLE The data never arrived
           "Buy BTC." — with the market endpoint down.
           ✗ could not establish bid, ask, order book for BTCUSDT — this is not a
             judgement about the order, it is the absence of one. A failed read is
             not permission.

1 of 7 orders cleared. The other 6 would all have been accepted by the venue.
```

That last line is the whole argument. **None of those six orders would have been
rejected.** They are well-formed, affordable, and the exchange takes them.

## Why this exists

Agent OS gives an agent spot, margin, convert and futures authority through one
MCP connection. That is the right thing to build, and it moves the dangerous
part somewhere new: the model now decides *when* and *how much*, and models are
bad at exactly the parts of that which are arithmetic rather than judgement.

An LLM does not know that the quote it is reasoning from is forty seconds old.
It does not walk an order book to find out what its own order does to the price
it gets. It cannot tell a `null` from a `0`, so a market-data request that
quietly failed reads as a market that is quietly fine.

The venue will not save you here. A venue rejects malformed orders — wrong lot
size, insufficient balance. It accepts, without comment, an order that walks the
book 300 bps, an order priced off stale data, and an order that puts 40% of an
account into one position. Those are the expensive ones, and nothing was
checking them.

So: a guard that runs *before* execution, holds no credentials, and cannot move
funds even if it is wrong.

### The rule that matters most

> **A failed read is not permission.**

`UNREADABLE` is a distinct outcome from `REFUSED` because they call for
different responses. `REFUSED` means the order is wrong — reprice it, resize it,
pick another moment. `UNREADABLE` means we never established anything: the order
may be perfect, and we cannot say. An agent told "refused" goes looking for a
better price. An agent told "unreadable" should stop and retry the read.

Most guards collapse the two, or worse, treat a failed fetch as an empty result
and pass it through as though the market were fine. This one is built around not
doing that. Anything it could not read comes back `null` and is named.

## Install

```bash
git clone https://github.com/tinoxbt/preflight-mcp.git
cd preflight-mcp
npm install
npm run build
```

Add it to Claude Code:

```bash
claude mcp add preflight -- node /absolute/path/to/preflight-mcp/dist/server.js
```

Or in `claude_desktop_config.json` / any MCP client:

```json
{
  "mcpServers": {
    "preflight": {
      "command": "node",
      "args": ["/absolute/path/to/preflight-mcp/dist/server.js"]
    }
  }
}
```

**No API key, no account, no OAuth.** It reads public market data only.

## Use it with Binance MCP

The point is to pair it with the server that actually executes:

```bash
claude mcp add binance-mcp-server --transport http https://agent.binance.com/mcp/agentic
claude mcp add preflight -- node /absolute/path/to/preflight-mcp/dist/server.js
```

Then give the agent one instruction:

> Before placing any order through the Binance MCP server, call
> `preflight_trade` with the same symbol, side and quantity. If the verdict is
> not `CLEARED`, do not place the order — report the finding instead.

The division of labour is deliberate. Binance MCP holds the OAuth session and
places orders. preflight-mcp holds nothing and places nothing. A bug in the
guard cannot cost you money; it can only cost you a trade.

## Tools

| Tool | What it does |
|---|---|
| `preflight_trade` | The gate. Symbol, side, quantity, optional limit price and balance → `CLEARED` / `REFUSED` / `UNREADABLE` plus findings and everything observed. |
| `market_snapshot` | The facts a decision rests on, with their age stated. Unread fields are `null` and listed in `missing`, never filled with zero. |
| `preflight_rules` | Every active threshold and the environment variable that changes it. A guard nobody can inspect is a guard nobody can argue with. |

## The rules

| Rule | Default | Env | Why |
|---|---|---|---|
| `stale_data` | 5 s | `PREFLIGHT_MAX_AGE_MS` | A quote older than this is history, not a price. |
| `wide_spread` | 50 bps | `PREFLIGHT_MAX_SPREAD_BPS` | Crossing it costs more than the trade is likely to make. |
| `slippage` | 100 bps | `PREFLIGHT_MAX_SLIPPAGE_BPS` | What the order does to its own price, walked across the live book. |
| `book_too_thin` | — | — | The order is larger than the visible book. Absence, not expense — reported separately. |
| `position_size` | 25% of free balance | `PREFLIGHT_MAX_BALANCE_BPS` | Past this it is a position decision. Skipped entirely unless a balance is supplied. |
| `price_band` | 20% outside 24h range | `PREFLIGHT_MAX_BAND_BPS` | A fat-finger catch for limit orders, judged against the pair's own day. |
| `not_trading`, `below_min_notional`, `step_size` | from the venue | — | The venue's own filters, checked before it has to reject the order. |

Defaults lean towards refusing, because the two errors do not cost the same. A
refusal costs a round trip and a message. A bad fill costs money and does not
come back.

`UNREADABLE` is not in the table because it is not a rule. It is what happens
when the facts cannot be established, and it always refuses.

## Design notes

**Slippage is measured, not assumed.** `walkBook` consumes real levels and
returns the average fill price across all of them. The top of book is not the
price a 500 BTC order gets.

**Both sides of the book.** A buy walks the asks, a sell walks the bids. A guard
that checked one side would clear a sell into a hollow bid stack.

**Position size is skipped, not guessed.** Omit `quoteBalance` and that rule
does not run. Inventing a balance would be the exact failure this project is
about.

**Every refusal cites its number.** There is a test asserting it. A refusal an
operator cannot check is an obstacle rather than a safeguard.

**Symbol filters are cached for an hour — successes only.** A failed lookup is
never cached as "no filters", which would turn one bad minute into an hour of
orders checked against nothing.

## Tests

```bash
npm test
```

Twelve tests over the pure rules, each one a thing that actually goes wrong when
an agent trades unattended: stale quotes, hollow books, wrong side, fat fingers,
dust, absent data. They run against fixtures rather than a venue, so they are
deterministic and need no network.

```bash
npm run demo   # the scenarios above, against the live venue
```

## Limitations, stated plainly

- **Spot only.** Futures and margin have their own failure modes — leverage,
  liquidation distance, funding — and this build does not model them. It should.
- **The visible book is not the whole book.** Iceberg and hidden liquidity mean
  `book_too_thin` can be pessimistic. That is the safe direction, but it is a
  real limitation, not a feature.
- **It cannot enforce anything.** It is advice an agent must be instructed to
  ask for. A guard that could enforce would need to sit in the execution path
  and hold credentials, which is the trade-off this deliberately does not make.
- **Public data host by default.** `api.binance.com` returns HTTP 451 from many
  hosts; the default is Binance's public market-data mirror
  (`data-api.binance.vision`), same endpoints and numbers. Override with
  `BINANCE_API_BASE`.

## Provenance

The rules are not invented for a hackathon. They are ported from a Rust mint
sniper the author has been running against live money, where the same principle
was learned the expensive way — an unreadable value read as zero cost 229,074
gas across six wallets in a single run. That codebase's preflight module refuses
to arm on a value it could not establish, for the same reason this one refuses
to clear a trade.

MIT.

TDQS

A3.9/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct role: preflight_trade returns a decision, market_snapshot provides raw market data, and preflight_rules exposes configurable thresholds. There is no overlap or ambiguity about which tool to call.

Naming Consistency4/5

Two tools share the preflight_ prefix and all names use clear, lowercase snake_case. market_snapshot breaks the prefix pattern but is still intuitively named and consistent in style.

Tool Count5/5

Three tools is a well-scoped set for the server's purpose: one decision tool, one data source, and one rules reference. Each tool earns its place with no unnecessary clutter.

Completeness5/5

The tool surface covers the full preflight workflow: understand the rules, inspect the market snapshot, and get a decision before placing an order. No obvious dead ends or missing operations are apparent.

Maintenance

ActivityMaintained
ResponsivenessNo issues