The Model Context Protocol (MCP) is an open standard for connecting AI agents to tools and data. Instead of wiring an agent to one bespoke API, you run an MCP server that advertises a set of tools. The agent reads that list when it connects and calls any tool by name, with typed arguments and typed results.
A trading MCP server applies that idea to a trading platform. It exposes actions like listing accounts, reading positions and placing an order as MCP tools. An agent connected to it can read current state and propose the next order in plain tool calls. The server is not a strategy and not a bot. It is the typed surface an agent talks to, plus the transport that carries the calls.
MCP in one paragraph: the protocol under the tools
MCP is an open standard built on JSON-RPC 2.0, introduced by Anthropic in late 2024 and now spoken by a range of agent clients, Claude among them. A server can expose three kinds of primitive: tools (executable actions), resources (read-only data an agent can load) and prompts (reusable templates). A trading server in v0 uses the tools primitive only. The moving parts are a host application, such as Claude Desktop, that runs an MCP client, and that client connects to your server. Discovery is a fixed handshake: on connect the client calls tools/list to learn what the server can do, then invokes any tool with tools/call. That standardization is the whole point. The same protocol and the same two verbs reach a trading account, a file store or a database without writing new glue for each one.
The tool surface
The useful mental model is a menu of typed functions. A trading MCP server splits its tools into reads and writes. Reads return state and never change anything: accounts, positions, orders, account state, guardrail status. Writes request a change: place an order, cancel one, close a position, flatten the account. Each tool declares its argument schema, so the agent knows that place_order needs an account, an instrument, a side and a quantity, and that the quantity is an integer.
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.
The write tools declare a fuller schema than a quantity alone. place_order accepts an order_type (MARKET by default, plus LIMIT and STOPMARKET), an optional limit_px and stop_px for those types, and a tif (DAY by default, or GTC). The agent generates all of it from a language model, so the schema is the first line of defense against a malformed call.
# place_order arguments (typed, validated before the guardrails run) account string sim instrument string MES 09-26 side enum BUY or SELL qty integer 2 order_type enum MARKET (default), LIMIT, STOPMARKET limit_px float optional, required for LIMIT stop_px float optional, required for STOPMARKET tif enum DAY (default) or GTC # a zero or negative qty is rejected before any guardrail runs place_order account=sim instrument="MES 09-26" side=BUY qty=0 -> SCHEMA_INVALID
The place_order argument schema. Field names match the daemon exactly.
A qty that is not a positive integer, or a side that is not BUY or SELL, is rejected with SCHEMA_INVALID before any risk logic runs. Schema validation is cheap and deterministic, and it catches a whole class of mistakes that prompt instructions cannot reliably prevent.
Two transports: stdio and streamable HTTP
MCP servers speak one of two transports, and both carry JSON-RPC 2.0. Over stdio the server runs as a local subprocess of the client and they exchange messages on standard input and output. This is what a desktop client like Claude Desktop uses: it launches the server, talks to it directly, and nothing touches the network. Over streamable HTTP (spec revision 2025-03-26, which superseded the older HTTP with server-sent-events transport) the server listens on a local port and a custom agent or script connects to that single endpoint, streaming responses over optional server-sent events. This suits your own code, a local automation, or a client on the same machine that prefers HTTP.
Registering the stdio server with a client is a small config block. For Claude Desktop it goes in claude_desktop_config.json:
{
"mcpServers": {
"pitbridge": {
"command": "uv",
"args": ["run", "--directory", "/ABSOLUTE/PATH/TO/pitbridge/daemon",
"pitbridge", "mcp",
"--config", "/ABSOLUTE/PATH/TO/config.toml"],
"env": { "PITBRIDGE_HOME": "/ABSOLUTE/PATH/TO/pitbridge-test" }
}
}
}The Claude Desktop config block. Claude Code registers the same server from a shell.
claude mcp add pitbridge -- \ uv run --directory /ABSOLUTE/PATH/TO/pitbridge/daemon \ pitbridge mcp --config /ABSOLUTE/PATH/TO/config.toml
The one-line Claude Code equivalent. Restart the client and it sees the nine tools.
Which transport, and a gotcha
The rule of thumb is short. Use stdio for a desktop client on your own machine, and streamable HTTP for your own agent, script or local automation. Both carry the same tools; the choice is only about who launches the server and how the client reaches it, not about what the agent can do. One honesty note on PitBridge itself: it serves MCP over stdio today, streamable HTTP is on the roadmap, and a custom agent can use the localhost REST surface meanwhile.
How it differs from a REST API, a webhook, and a broker API
A trading MCP server is not the only way to send an order. It is worth placing it next to the interfaces it replaces or sits beside. The distinguishing question is who decides the order and where a control can sit.
| interface | who decides the order | capability discovery | state-aware | where a control can sit |
|---|---|---|---|---|
| REST or broker API (NT8 ATI, IBKR API) | your code, wired in advance | none, you read the docs | you track it yourself | in your code, and skippable |
| Webhook | a fixed rule fires a preset order | none | no | at the receiver |
| Cloud MCP endpoint | agent, via a third-party server | tools/list | yes | on someone else’s box, in the network path |
| Local-first trading MCP (PitBridge) | agent proposes, daemon adjudicates | tools/list | yes | out-of-process guardrail on your machine, unskippable |
A broker or platform API is a direct order route with no reasoning layer and no built-in choke point. A webhook adds a fixed trigger, still with no live-state reasoning. MCP adds tool discovery and live-state reasoning, and, done local-first, a single enforced control point on your own machine. The deep dives live elsewhere: MCP vs webhooks argues the trigger-versus-conversation split, and local-first automation covers why the order route staying on your machine is the part that matters.
The lifecycle of one order intent
The important property of a trading MCP server is that a tool call is a request, not a guarantee. Walk one order intent through the path:
- The agent connects and reads the tool list.
- It calls read tools to build a picture of the account:
get_positions,get_orders,get_account_statefor realized loss and halt status. - It decides to act and calls
place_orderwith typed arguments:account,instrument,sideandqty. - The server validates the schema, then hands the request to the guardrail engine.
- The engine checks the request against your configured limits and returns an allow, or a block with a typed
reason_code. - Only an allowed order continues to the platform.
# read state, then place two orders. the second breaks the per-order cap.
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_ORDERExample agent tool calls against the local daemon. Real tool names and reason codes.
This is why the guardrail engine sits after the tool call, not inside the agent. A model can be instructed to respect a daily loss limit, but instructions are advisory; a check that runs in the daemon, out of the model’s reach, is enforced. The guardrails that an LLM cannot override spoke argues that mechanism in full.
What the guardrail chain checks, and the codes it returns
Every mutating call runs the same fixed pipeline: schema, then permission, then guardrails, then confirm, then submit, then audit. The guardrail stage is a frozen chain of twelve checks in a fixed evaluation order, with the kill switch first and human confirm last. Each check can emit a stable reason_code. The first blocking guardrail wins, and evaluation stops there.
| stage or guardrail | reason code(s) it can emit |
|---|---|
| engine shape checks (before the chain) | SCHEMA_INVALID, INVALID_QTY, UNKNOWN_ACCOUNT |
| kill_switch | KILL_SWITCH |
| link_down | LINK_DOWN |
| instrument_allowlist | INSTRUMENT_NOT_ALLOWED |
| position_caps | MAX_CONTRACTS_PER_ORDER, MAX_POSITION |
| trading_window | NO_TRADE_DAY, OUTSIDE_TRADING_WINDOW |
| daily_loss_halt | DAILY_LOSS_HALT |
| profit_lock | PROFIT_LOCK |
| cooldown | COOLDOWN_AFTER_LOSS, COOLDOWN_AFTER_ORDER |
| rate_limit | MAX_ORDERS_PER_MINUTE / _HOUR / _DAY |
| duplicate_protection | DUPLICATE_ORDER |
| human_confirm (last) | HUMAN_CONFIRM_FIRST_ORDER / _EVERY_LIVE_ORDER / _SIZE_INCREASE |
These codes are an API contract, not error strings. They are stable, machine-readable and append-only: renaming or removing one is a breaking change. A blocked order is returned as a structured result, blocked=true plus a reason_code and a human-readable detail, never a raised exception that would tear down the agent’s session. The four action outcomes an agent sees are SUBMITTED, BLOCKED, PENDING_CONFIRM and DONE. Because the code is typed, the agent can read why an order stopped and explain it rather than guessing. The guardrails page lists exactly what the engine refuses.
The security model of a trading MCP server
Connecting a language model to an order surface introduces risks a plain broker script does not have. Prompt injection, sometimes called tool poisoning, is when untrusted text the model reads persuades it to call a tool it should not. The confused-deputy problem is the general shape: a component acting for a user is tricked into using its authority for something the user never intended. A trading MCP server has to assume the driving model can be misled, and narrow the blast radius rather than trust it to behave.
- Tool-surface minimization. No tool to release the kill switch, arm live trading, or mutate a guardrail or config value exists on the agent surface, so a compromised or injected agent cannot widen its own authority.
- Out-of-process enforcement. The guardrail engine adjudicates every
place_orderregardless of what the model was told. That is the point of the guardrails an LLM cannot override. - Localhost bind and a pairing token. The daemon binds
127.0.0.1and the AddOn presents apairing_token, so no order is relayed off the machine. - De-risking always allowed. Cancel, close and flatten stay available even under a kill, so you can always get flat.
- A tamper-evident, hash-chained audit log.
audit verifyplus an anchor file catches tampering, including deletion of the tail of the log. - Separate account modes.
read_only,paperandliveare distinct states, so trying an agent never touches a funded account by accident.
Permissions and account modes
A trading MCP server should let you scope what an agent can reach, and two controls do most of the work. Tool permissions decide which tools are exposed at all: offer the read tools and withhold place_order entirely, which gives an agent full visibility and zero write access. Account modes decide which account the writes touch.
| mode | what a write does | how you reach it |
|---|---|---|
| read_only | rejected, reads only | the default safe state |
| paper | hits a simulation account | set the account mode in config |
| live | hits a funded account | requires a separately-shipped live plugin and an operator arm-live step, impossible from the agent surface |
On top of modes, the human_confirm gate adds a human in the loop, with variants for the first order of a session, every live-mode order, or any order that increases size beyond the session maximum so far. Read-only, paper and live are separate states on purpose, so that trying an agent never risks a funded account by accident.
A worked example you can run on a Mac, no NinjaTrader needed
You can exercise the whole surface on a Mac against a fake AddOn, a small simulator that stands in for the real NinjaTrader AddOn, with no Windows box and no market feed. Start with a config for one Sim101 paper account and a few guardrails.
[accounts.sim]
nt_account = "Sim101"
mode = "paper"
[accounts.sim.guardrails]
instruments = ["MES 09-26"]
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 minimal paper config. Real field names, taken from the testing guide.
Start the daemon, then connect the fake AddOn. Until an AddOn connects and sends its first account state, every order is blocked with LINK_DOWN. That is the reconcile-first rule: no queueing orders against a dead link. Once the link is fresh, the agent reads state and proposes orders. A valid order fills; an off-allowlist instrument is refused.
get_account_state -> link_up: true, day_pnl: 0.0 place_order account=sim instrument="MES 09-26" side=BUY qty=2 -> SUBMITTED, then FILLED place_order account=sim instrument="NQ 09-26" side=BUY qty=1 -> BLOCKED reason_code=INSTRUMENT_NOT_ALLOWED
The allowlist blocks an off-list instrument before the fake broker ever sees it.
Engage the kill switch from the CLI. A valid entry now returns KILL_SWITCH, but de-risking still works, so you can always get flat. Releasing the kill is operator-only.
pitbridge kill --reason "stepping away" place_order account=sim instrument="MES 09-26" side=BUY qty=1 -> BLOCKED reason_code=KILL_SWITCH flatten_account account=sim -> DONE (de-risking allowed under a kill) pitbridge unkill # release is CLI-only, off the agent surface
Under a kill, new entries stop but flatten still runs. The agent cannot lift the kill.
Every request, decision and outcome is written to an append-only, hash-chained audit log, so you can ask why any order decided the way it did and verify the chain is intact.
pitbridge audit why <order_id> # request, guardrail decision, submit result pitbridge audit verify # chain OK (N entries), tamper-evident
The audit log explains and verifies every decision after the fact.
For the full Claude setup against NinjaTrader 8, read connect Claude to NinjaTrader 8 safely. When your platform and prop firms are supported, tell us on the waitlist. PitBridge is trading infrastructure, not financial advice: it enforces the rules you configure and does not promise any trading outcome.