Safety model

hood-mcp (the data server) never signs anything โ€” every tool is read-only. hood-mcp-trading is a separate opt-in binary with four gates, in this order, before a single wei moves.

The four gates

  1. 1

    Kill switch โ€” the process itself

    hood-mcp-trading checks HOOD_MCP_ENABLE_TRADING === "1" before it even connects to the MCP transport. Set a wallet key without this and the process throws on startup and exits โ€” it will not silently run read-only.

  2. 2

    Eligibility gate โ€” Stock Token acquisitions

    Stock Tokens are tokenized debt securities (issuer: Robinhood Assets (Jersey) Ltd) and may not be offered, sold, or delivered to US persons (additional limits: Canada, UK, Switzerland). Any swap whose output is a canonical Stock Token throws unless HOOD_MCP_ACKNOWLEDGE_ELIGIBILITY=1 โ€” the operator's explicit affirmation of eligibility. Selling a Stock Token, and all memecoin trades, are never gated by this.

  3. 3

    Spend caps โ€” checked before signing

    Every mutating call is valued in USD (via USDG) and checked against HOOD_MCP_MAX_SPEND_USDG (per single call, default $25) and HOOD_MCP_MAX_SESSION_USDG (cumulative for the process's lifetime, default $100). The check runs before anything is signed โ€” a rejected call never reaches the wallet. On mainnet, a spend with no USDG price route is rejected outright rather than treated as zero-value.

  4. 4

    Confirm gate โ€” every mutating tool, every time

    execute_swap and transfer_usdg always return a simulation (recipient, amount, token, minimum received, session budget remaining) on the first call. Nothing is broadcast until the exact same call is repeated with confirm: true. There is no "trust me" flag โ€” an agent (or the human behind it) sees the concrete numbers before anything moves.

What a blocked call looks like

execute_swap(tokenOut: "AAPL")
โ†’
eligibility not acknowledged
โ†’
isError: true, nothing signed
execute_swap(amountIn: "1000 USDG")
โ†’
$1000 > $25 per-call cap
โ†’
isError: true, nothing signed
execute_swap(amountIn: "10 USDG")
โ†’
under cap, simulation returned
โ†’
confirm: true
โ†’
broadcast, receipt returned

Every path above is exercised by real tests against live network reads โ€” see tests/trading-guards.test.ts in the repo.

What's NOT guarded

Be precise about the boundary. These are true regardless of caps or gates:

Slippage is your risk, not a cap

slippageBps defaults to 50 (0.5%) but is caller-controlled up to 5000 (50%). A high slippage tolerance can still result in a bad fill within your USD cap.

The session cap resets on restart

HOOD_MCP_MAX_SESSION_USDG is an in-memory counter. Restarting the trading server process resets it to zero โ€” it is not a persistent ledger.

The wallet key is the real boundary

Anyone with ROBINHOOD_CHAIN_PRIVATE_KEY can bypass this server entirely and sign directly. Treat the key with the same care you would give any hot wallet key.

Testnet spends are unvalued, not uncapped

Testnet assets have no USD price, so their notional counts as $0 against the cap โ€” the confirm gate is the real guard there, not the dollar limit.