Connecting Claude to NinjaTrader 8 is not the hard part. Getting the two to talk takes a small config block. The hard part is doing it in a way where a bad tool call cannot quietly send a real order to a funded account. This walks through the whole path: install the local bridge, write a safe config, wire Claude to it, connect the link, and confirm that a guardrail engine on your machine blocks an order that breaks your limits before NinjaTrader ever sees it.
The pieces you need
Three things make the connection. NinjaTrader 8, running as usual with its own accounts and data. A local bridge, the PitBridge daemon, which runs an MCP server, a guardrail engine and a small NinjaTrader AddOn that carries orders into the platform. And an MCP client, which is the thing Claude runs inside, either Claude Desktop or Claude Code.
The bridge is the important piece. It is the only component that both speaks to Claude and speaks to NT8, and it is where every order is checked. Nothing about this setup asks you to send your account details to a hosted service. The local-first bridge keeps the MCP server, the guardrail engine and your keys in one process on your own machine.
Example decisions: ALLOW buy 2 MES, all rules pass. BLOCK buy 10 MES, size 10 exceeds max_contracts_per_order 2.
The order route: the agent proposes, the guardrail engine adjudicates, and only an allowed order reaches NinjaTrader 8. The block is shown as plainly as the allow.
Before you start: what you need
Four things, once. You will not need any of them again after the first run.
- macOS, or the Windows box where NinjaTrader 8 runs, with the uv package manager installed.
- The PitBridge daemon repository checked out. Every daemon command runs from the daemon directory.
- NinjaTrader 8 with a Sim101 account. Real routing into NT8 is milestone M4. On a Mac you exercise the whole safety kernel against a bundled fake AddOn instead.
- An MCP-capable client: Claude Desktop or Claude Code.
Install uv with the one-line installer, then sync the daemon and check the version. This build runs in paper and simulation only: arm-live refuses, because the live-execution plugin ships separately in a later milestone.
# from the daemon directory
curl -LsSf https://astral.sh/uv/install.sh | sh
cd pitbridge/daemon
uv sync --extra dev # create the venv, install deps
uv run pitbridge --version # prints "pitbridge 0.1.0"Install and verify. You can also put the CLI on your PATH with uv tool install, after which the uv run prefix can be dropped.
Write a minimal, safe config
PitBridge reads one TOML file that describes the daemon bind, your account, and its guardrails. Keep it under a scratch PITBRIDGE_HOME so nothing touches your real ~/.pitbridge. This config defines a single Sim101 paper account with realistic limits.
[daemon]
bind = "127.0.0.1" # localhost only
port = 8873
paired_mode = false
pairing_token = "pb_pair_demo" # the AddOn presents this token
[accounts.sim]
nt_account = "Sim101" # the simulated NT account name
mode = "paper" # read_only | paper | live
profile = "eval" # eval | funded
[accounts.sim.guardrails]
instruments = ["MES 09-26"] # allowlist
max_contracts_per_order = 5
max_position = 10
daily_loss_halt = 400
trading_window = { tz = "America/New_York", open = "09:30", close = "16:00" }A real config.toml. Keys match the daemon schema. mode is read_only, paper or live, and live is refused in this build.
Every guardrail you can set
The block above is the short version. The full guardrail surface, under [accounts.sim.guardrails], is the definitive reference below. Every setting is optional: leave one out and that check simply does not run.
| setting | value | what it enforces |
|---|---|---|
instruments | list of contracts | Allowlist. Only the listed contracts may be traded, matched on the exact string including expiry. |
max_contracts_per_order | integer >= 1 | Rejects any single order larger than this. |
max_position | integer >= 1 | Caps the net position per instrument and account. |
daily_loss_halt | dollars > 0 | Halts the day once day P&L reaches minus this amount. |
profit_lock | dollars > 0 | Banks the day once day P&L reaches plus this amount. |
trading_window | tz, open, close | Orders are allowed only inside this session window, early-close aware. |
max_orders | per_minute, per_hour, per_day | Rate limits on order submission over each rolling window. |
cooldown_after_loss_s | seconds >= 0 | Pauses new entries for this long after a losing execution. |
cooldown_after_order_s | seconds >= 0 | Pauses new entries for this long after any order. |
duplicate_window_s | seconds >= 0 | Rejects an identical account, instrument, side and qty inside the window. |
human_confirm | off | first_order | every_live_order | size_increase | Holds a matching order for CLI approval instead of submitting it. |
Two settings live at account level rather than inside the guardrails block. day_halt_sticky defaults to true, so a hit loss limit or profit lock ends the trading day rather than being recomputed each check. on_link_down chooses what happens when the AddOn link drops: none, notify, or flatten_on_reconnect.
Start on a simulation account
Before an agent touches anything funded, keep the bridge pointed at a NinjaTrader simulation account such as Sim101, exactly as the config above does. Better still, start in read-only mode so the agent can read state and reason out loud without any write tools exposed at all. When you are ready to let it place orders, do it against the simulation account first and watch how it behaves across a full session.
Start the daemon, and the one thing not to do
The daemon has two surfaces. pitbridge run gives the REST and WebSocket surface. pitbridge mcp gives the MCP stdio surface that Claude drives. The important part: the MCP stdio server is itself the daemon, and it serves the AddOn link on the same port. When you wire Claude in the next section, Claude launches pitbridge mcp for you. You do not separately run pitbridge run.
Point Claude at the local MCP server
Claude Desktop reads a small configuration file that lists the MCP servers it should connect to. You add one entry for PitBridge that runs the daemon’s mcp command over stdio and points it at your config. Replace the two absolute paths with your own checkout and config location.
{
"mcpServers": {
"pitbridge": {
"command": "uv",
"args": [
"run", "--directory", "/ABSOLUTE/PATH/TO/pitbridge/daemon",
"pitbridge", "mcp",
"--config", "/ABSOLUTE/PATH/TO/pitbridge-test/config.toml"
],
"env": { "PITBRIDGE_HOME": "/ABSOLUTE/PATH/TO/pitbridge-test" }
}
}
}Claude Desktop config. Runnable once you fill in your own absolute paths.
Claude Code registers the same server from a shell, with one command:
claude mcp add pitbridge -- \ uv run --directory /ABSOLUTE/PATH/TO/pitbridge/daemon \ pitbridge mcp --config /ABSOLUTE/PATH/TO/pitbridge-test/config.toml
Claude Code one-liner. Same server, registered from the command line.
Where the config file lives
On macOS the Claude Desktop file is at ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows it is under %APPDATA%\Claude\. One gotcha catches most people: Claude Desktop launches these configs with a minimal PATH, so a bare uv in command can fail to resolve. If the server will not start, use an absolute path to uv, for example ~/.local/bin/uv.
Restart the client fully and Claude sees nine tools. Five read state and never change anything. Four request a change, and every place_order still passes the guardrail engine.
reads5
get_accountsget_positionsget_ordersget_account_stateget_guardrail_status
Return state. They never change anything.
writes4
place_ordercancel_orderclose_positionflatten_account
Request a change. Every place_order still passes the guardrail engine.
There is deliberately no tool to lift the kill switch, arm live trading, or change a guardrail. Those are operator-only and live on the CLI. This is the difference between an agent that can look and propose, and an agent wired straight into a live order router.
Connect the link so orders have somewhere to go
Until an AddOn connects and sends its first account state, the daemon blocks every order with LINK_DOWN. This is the reconcile-first rule: no queueing orders against a dead link. On a Mac you provide that link with the fake AddOn, pointed at the same port and token as your config.
uv run python ../tools/fake_addon.py \ --host 127.0.0.1 --port 8873 \ --token pb_pair_demo --account Sim101
The fake AddOn provides the link so Claude’s orders have somewhere to go.
The fake AddOn is deliberately dumb: it auto-acks and fills orders, replies to snapshots, and keeps the link fresh. Its fills are synthetic and instant, with no market data and no slippage, so it proves the safety kernel, not trading performance. On a Windows box with NinjaTrader, the real C# AddOn from milestone M4 replaces it and routes orders into the platform.
Verify the connection
Ask Claude in plain language, “what accounts do I have?” and “what is my guardrail status?”. It calls get_accounts and get_guardrail_status, and you should see link_up: true. Cross-check the same picture from the CLI at any time.
uv run pitbridge status --config "$PITBRIDGE_HOME/config.toml" PitBridge status daemon : bind 127.0.0.1:8873 (paired_mode=false) kill : released accounts : - sim [Sim101] mode=paper profile=eval link_up=true day_halted=false open orders (0): (none)
The status command. link_up=true and kill released means the link is fresh and orders will be adjudicated normally.
What safely means here
Safely does not mean the agent is smart. It means the agent cannot do something you did not permit, no matter what it decides. Between Claude and NinjaTrader sits the guardrail engine, and it checks every place_order against the limits you configured: a per-order contract cap, a position cap, a daily loss halt, an instrument allowlist, and a kill switch you can hit at any time. The agent never sees around these checks. It calls the tool, the engine adjudicates, and only an allowed order reaches NT8.
This is why the connection is built the way it is. A language model can be told to respect a limit, but a told limit is advice. A limit enforced in the daemon, out of the model’s reach, is a control. The guardrails page lists exactly what the engine refuses, and the security model covers where each part runs and how the local token works.
A blocked order, on purpose
The moment that shows the setup working is a blocked order. Ask Claude in plain language to check the account, buy two contracts, then buy twenty. It issues three tool calls. The first two pass. The third breaks the per-order contract cap you configured, and the engine refuses it before NinjaTrader sees anything.
# claude checks state, then places two orders against the sim account # fills are synthetic sim fills from the fake AddOn (Rule 4.41 applies) get_account_state -> link_up: true, day_pnl: 0.0, day_halted: false place_order account=sim instrument="MES 09-26" side=BUY qty=2 -> SUBMITTED, then FILLED filled_qty=2 place_order account=sim instrument="MES 09-26" side=BUY qty=20 -> BLOCKED reason_code=MAX_CONTRACTS_PER_ORDER
Example agent tool calls against the local daemon. Real tool names and reason codes. Fills are synthetic simulation fills, not live results. The block returns as a structured result Claude can read and explain, not an error that kills the chat.
A refused order is the system doing its job, not an error to hide. The block comes back as a typed reason_code, so the agent can read it, explain why the order stopped, and move on. When you connect Claude to NinjaTrader 8 this way, you are not trusting the model to stay inside your risk. You are making it impossible for the model to leave.
Every reason an order can be refused
Every block returns a stable, machine-readable reason_code the agent can read and explain. The evaluation order is frozen: the kill switch is always checked first, and human confirmation is always last, so a config change cannot reshuffle the safety chain. Below are the codes the engine emits.
| reason_code | what it means |
|---|---|
KILL_SWITCH | Kill switch engaged. New entries are blocked. Release is CLI-only. |
LINK_DOWN | AddOn link is down or has not sent first state. Orders hard-reject. |
INSTRUMENT_NOT_ALLOWED | Instrument is not in the account’s allowlist. |
MAX_CONTRACTS_PER_ORDER | Order qty exceeds the per-order contract cap. |
MAX_POSITION | Projected net position exceeds max position for that instrument and account. |
OUTSIDE_TRADING_WINDOW | Outside the configured session window. |
NO_TRADE_DAY | Holiday-calendar closure, or a conservative half-day policy. |
DAILY_LOSS_HALT | Day P&L is at or under minus your daily_loss_halt. |
PROFIT_LOCK | Day P&L is at or over plus your profit_lock. The day is banked. |
COOLDOWN_AFTER_LOSS | Inside the cooldown window since the last losing execution. |
COOLDOWN_AFTER_ORDER | Inside the cooldown window since the last order. |
MAX_ORDERS_PER_MINUTE / HOUR / DAY | Order rate limit for that rolling window has been reached. |
DUPLICATE_ORDER | Same account, instrument, side and qty inside the duplicate window. |
HUMAN_CONFIRM_* | Confirmation is required before this order can proceed. |
INVALID_QTY | Qty is not an integer of at least 1. Over REST a malformed payload is rejected SCHEMA_INVALID before any guardrail runs, so a negative BUY cannot become a disguised SELL. |
The kill switch and getting flat
The kill switch is the blunt instrument. Run uv run pitbridge kill --reason "..." and every new entry is blocked with KILL_SWITCH. De-risking is deliberately still allowed under a kill, so cancel_order, close_position and flatten_account still work and you can always get flat. Releasing the kill is CLI-only with uv run pitbridge unkill. Claude has no tool to lift it, by design. The engine enforces the limits you configure; it does not promise any trading outcome.
Optional: require human confirmation
If you want a person in the loop, set human_confirm to first_order, every_live_order, or size_increase in the guardrails block. A matching order comes back PENDING_CONFIRM instead of being submitted. Approve it from the CLI with uv run pitbridge confirm <order_id>, which re-checks the kill switch, the halt state and the guardrails at confirm time. It auto-rejects after 60 seconds if you do nothing.
Explain and audit every decision
Every request, decision and outcome is written to an append-only, hash-chained audit log. Take an order_id and run uv run pitbridge audit why <order_id> to see the request, the guardrail decision, and the submit result for exactly why an order passed or blocked. Run uv run pitbridge audit verify to prove the whole chain is intact and untampered. It reports chain OK with the entry count, and an anchor file catches anyone deleting the tail of the log.
Troubleshooting
Most first-run problems come from the link or the client PATH, not the guardrails. This table maps the common symptoms to a cause and a fix.
| symptom | cause | fix |
|---|---|---|
Every order returns LINK_DOWN | No AddOn connected, or it has not sent first account state. | Start the fake AddOn, or the real AddOn, on the same port. |
| Claude shows no PitBridge tools | Client not restarted, or uv path wrong. | Fully quit and reopen the client. Use an absolute path to uv. |
| Server fails to start in Claude Desktop | Minimal PATH cannot resolve a bare uv. | Put an absolute uv path in the command field. |
port already in use or double behavior | Running pitbridge run and pitbridge mcp on the same port. | Run only one surface. |
INSTRUMENT_NOT_ALLOWED on a valid contract | Contract is not in the instruments allowlist. | Add the exact string including expiry, for example MES 09-26. |
OUTSIDE_TRADING_WINDOW unexpectedly | Local time differs from trading_window.tz. | Widen the window or check the timezone. |
Valid order blocked KILL_SWITCH | The kill is still engaged from earlier testing. | Run pitbridge unkill. |
INVALID_QTY or SCHEMA_INVALID | Zero, negative, or non-integer qty. | Send a qty of at least 1. |
A note on hosted alternatives
There is more than one way to give an agent access to NinjaTrader. CrossTrade, for example, is a hosted MCP path. PitBridge takes a different stance: it is local-first and guardrail-first, so the MCP server, the checks and your keys stay on your machine and the block is the product. Which one fits depends on whether you want the bridge in the cloud or on your own hardware.
When your NinjaTrader setup is supported, tell us on the waitlist which platform and prop firms you use. PitBridge is trading infrastructure, not financial advice: it enforces the limits you configure and does not promise any trading outcome.