Skip to main content
Glama
tinoxbt

preflight-mcp

by tinoxbt

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 — 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

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

Add it to Claude Code:

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

Or in claude_desktop_config.json / any MCP client:

{
  "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:

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

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.

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.