AI agents can now do more than answer questions.
They can interact with external applications through tools.
Robinhood's Trading MCP is one example: an external AI agent can connect to Robinhood and use supported account, portfolio, market-data, watchlist, equities, options, crypto, scanner, alert, and order-related tools. Robinhood also provides trade-approval controls for agentic trading.
That creates a new engineering problem.
The difficult part is not:
AI
↓
Place Order
The difficult part is:
AI Agent
↓
Trading MCP
↓
Permission Layer
↓
Risk Gateway
↓
Approval / Policy
↓
Order Execution
↓
Audit
↓
State Reconciliation
For this project, I am building a reusable Robinhood Trading MCP Risk Gateway with TypeScript.
The goal is to demonstrate how I would put deterministic controls between an AI agent and trading execution.
This is not an LLM stock-prediction system.
It is an execution-control system.
Why put a risk gateway between the agent and the broker?
An AI model can interpret instructions and select tools.
It should not automatically become the final authority over trading limits.
Consider:
User:
"Buy $5,000 of this stock."
The agent might generate:
symbol = XYZ
side = buy
amount = $5,000
But the application's risk policy might say:
maximum order = $1,000
The gateway should reject the request.
AI Agent
↓
Trade Intent
↓
Risk Gateway
↓
REJECTED
This is the central design principle:
The agent can propose an action, but deterministic application code decides whether that action is allowed.
The architecture
The initial architecture is:
+----------------------+
| AI Agent |
+----------+-----------+
|
v
+----------------------+
| Robinhood Trading |
| MCP |
+----------+-----------+
|
v
+----------------------+
| Tool Permission |
| Layer |
+----------+-----------+
|
v
+----------------------+
| Trade Intent |
| Validation |
+----------+-----------+
|
v
+----------------------+
| Risk Gateway |
+----------+-----------+
|
v
+----------------------+
| Approval / Policy |
+----------+-----------+
|
v
+----------------------+
| Execution |
+----------+-----------+
|
v
+----------------------+
| Audit / State |
+----------------------+
The gateway sits in the middle.
That makes the execution boundary explicit.
Robinhood's external-agent model
Robinhood's current Trading MCP documentation describes MCP as a mechanism for connecting an external AI agent to Robinhood so the agent can access information and take supported actions. Robinhood documents connections for platforms including Claude, ChatGPT, Codex, Cursor, Grok, Perplexity, OpenClaw, Replit, and others.
The external agent uses a dedicated MCP account for trading.
The agent can read information from the user's Robinhood accounts, while trading through the dedicated Agentic/MCP account.
That distinction is important when designing an application around the integration.
Read tools versus write tools
The first permission boundary I want is the separation between reading and changing state.
Conceptually:
Read
----
get_accounts
get_portfolio
get_watchlists
get_market_data
get_positions
get_history
versus:
Write
-----
place order
cancel order
modify order
Robinhood's current tool documentation exposes separate categories of account, portfolio, market-data, trading and advanced-order capabilities.
That allows the application to define permissions independently.
A research agent might receive only read permissions.
A portfolio assistant might receive read plus proposal permissions.
A trading agent might receive execution permissions only after additional policy checks.
The project structure
I want the repository to remain simple:
robinhood-mcp-risk-gateway/
├── src/
│ ├── agent/
│ │ └── agent-types.ts
│ │
│ ├── mcp/
│ │ ├── mcp-client.ts
│ │ └── tool-registry.ts
│ │
│ ├── permissions/
│ │ ├── permission.ts
│ │ └── permission-engine.ts
│ │
│ ├── trades/
│ │ ├── trade-intent.ts
│ │ ├── trade-validator.ts
│ │ └── trade-service.ts
│ │
│ ├── risk/
│ │ ├── risk-policy.ts
│ │ └── risk-engine.ts
│ │
│ ├── approvals/
│ │ └── approval-service.ts
│ │
│ ├── audit/
│ │ └── audit-log.ts
│ │
│ ├── state/
│ │ └── execution-state.ts
│ │
│ └── index.ts
│
├── test/
│ ├── unit/
│ └── integration/
│
├── examples/
├── docs/
├── package.json
├── tsconfig.json
├── .env.example
└── README.md
The core business rules should not be coupled directly to the AI model.
Structured trade intent
I don't want the rest of the application to consume free-form model output.
Instead, convert it into a typed intent.
For example:
type TradeIntent = {
symbol: string;
side: "buy" | "sell";
orderType: "market" | "limit";
quantity?: number;
notional?: number;
limitPrice?: number;
rationale?: string;
};
The application can then validate the object before it reaches execution.
The flow becomes:
Natural Language
↓
AI Agent
↓
Structured Trade Intent
↓
Schema Validation
↓
Risk Validation
The AI's output becomes an input to the software system.
It is not the software system itself.
Why structured output matters
Compare these two outputs.
Free-form:
"Let's buy a meaningful amount of XYZ because momentum looks strong."
Structured:
{
symbol: "XYZ",
side: "buy",
orderType: "market",
notional: 500
}
The second can be validated.
For example:
max order = $1,000
requested = $500
result = ALLOWED
But:
max order = $1,000
requested = $5,000
result = REJECTED
This is much easier to test.
Risk policy
The risk policy should be deterministic.
A basic model:
type RiskPolicy = {
maxOrderNotional: number;
maxPositionNotional: number;
maxPortfolioExposure: number;
maxDailyLoss: number;
maxTradesPerDay: number;
allowedSymbols: Set<string>;
};
The risk engine can evaluate:
order size
position size
portfolio exposure
daily trading count
daily loss
symbol restrictions
The exact rules can be extended later.
Risk evaluation
The core API can be small:
type RiskDecision =
| {
allowed: true;
checks: string[];
}
| {
allowed: false;
reason: string;
failedChecks: string[];
};
Then:
function evaluateTrade(
trade: TradeIntent,
policy: RiskPolicy,
): RiskDecision {
// deterministic checks
}
The output should explain why a trade was accepted or rejected.
Example risk checks
A trade might go through:
Trade Intent
|
+--> symbol allowed?
|
+--> order size allowed?
|
+--> position limit allowed?
|
+--> portfolio exposure allowed?
|
+--> daily loss limit allowed?
|
+--> trading limit allowed?
Only when the required checks pass does the trade continue.
All checks pass
↓
Execution allowed
Symbol allowlists
One simple control is restricting which assets the agent can trade.
For example:
const allowedSymbols = new Set([
"AAPL",
"NVDA",
"MSFT",
]);
Then:
if (!policy.allowedSymbols.has(trade.symbol)) {
return {
allowed: false,
reason: "SYMBOL_NOT_ALLOWED",
failedChecks: ["allowedSymbols"],
};
}
This is intentionally boring.
Risk controls should be boring.
Position limits
Suppose the application has:
maximum position = $2,500
and the current position is:
$2,200
A new:
$500
order should fail because it would push exposure beyond the configured limit.
Current position
$2,200
+
New order
$500
=
$2,700
Limit = $2,500
Result = REJECTED
The important part is that the decision is deterministic.
Portfolio exposure
The gateway can also enforce portfolio-level constraints.
For example:
Technology exposure: 48%
Maximum: 40%
An agent might still propose another technology trade.
The risk engine rejects it.
This creates a portfolio-aware execution boundary:
Agent
↓
Trade
↓
Portfolio State
↓
Risk Policy
↓
Decision
Daily loss controls
Another useful guardrail is a daily loss limit.
For example:
maximum daily loss = $500
If the application detects that the threshold has already been reached:
Trade Intent
↓
Risk Engine
↓
DAILY_LOSS_LIMIT
↓
Rejected
The agent does not get to override the policy.
Tool permissions
Risk is only one layer.
The agent also needs permissions.
I model capabilities such as:
READ_ACCOUNT
READ_PORTFOLIO
READ_MARKET_DATA
READ_WATCHLIST
PROPOSE_TRADE
PLACE_ORDER
CANCEL_ORDER
Then define which agent has which capability.
For example:
type AgentPermissions = {
readAccount: boolean;
readPortfolio: boolean;
readMarketData: boolean;
proposeTrade: boolean;
placeOrder: boolean;
cancelOrder: boolean;
};
A research agent can have:
readAccount = true
readPortfolio = true
readMarketData = true
placeOrder = false
An execution agent can be given a narrower set of explicitly required write permissions.
Human approval
Robinhood currently supports trade approvals for agentic trading. With approvals enabled, the agent can propose trades but the user must review and manually place them in Robinhood; with approvals disabled, eligible trades can be placed by the agent without manual confirmation of each order. Robinhood notes that certain trades may still require approval.
That maps naturally to two application modes.
Assisted mode
Agent
↓
Trade Intent
↓
Risk
↓
Approval Request
↓
Human
↓
Robinhood
Automated mode
Agent
↓
Trade Intent
↓
Risk
↓
Policy
↓
Robinhood
I would build assisted mode first.
Approval state
The application can represent approval explicitly:
type ApprovalStatus =
| "NOT_REQUIRED"
| "PENDING"
| "APPROVED"
| "REJECTED";
The trade lifecycle then becomes:
PROPOSED
↓
VALIDATED
↓
RISK_CHECKED
↓
APPROVAL_PENDING
↓
APPROVED
↓
SUBMITTED
Or:
RISK_CHECKED
↓
REJECTED
This makes the execution path easy to audit.
Order state
Once an order is submitted, the application still needs to track it.
A useful state machine:
id="robinhood-order-state"
PROPOSED
↓
VALIDATED
↓
APPROVED
↓
SUBMITTED
↓
OPEN
↓
FILLED
↓
RECONCILED
Failure paths:
SUBMITTED
├── REJECTED
├── CANCELED
└── FAILED
The important distinction is:
order submitted
versus:
order filled
The second requires actual execution state.
Audit logging
Every meaningful agent action should produce an audit record.
For example:
type AuditRecord = {
id: string;
timestamp: string;
agentId: string;
action: string;
tool: string;
inputHash?: string;
riskDecision?: string;
approvalStatus?: string;
result?: string;
error?: string;
};
The goal is to answer:
Why did this order happen?
An audit trail should let us reconstruct:
User Request
↓
Agent Decision
↓
Tool Call
↓
Risk Decision
↓
Approval
↓
Order
↓
Result
The AI should not bypass the gateway
The architecture should enforce one execution route.
Not:
Agent
├── MCP
├── Direct API
└── Another execution path
Instead:
Agent
↓
Trading MCP
↓
Risk Gateway
↓
Execution
There should be one clearly defined path to a trading action.
That makes permissions, logging, testing, and monitoring much easier.
Market data and account data
Robinhood's current agent tooling includes account, portfolio, watchlist, market-data, equities, options, crypto, scanner, alerts, and advanced order capabilities.
That means the agent can potentially build decisions from multiple sources:
Portfolio
+
Market Data
+
Watchlist
+
Trading Rules
↓
Trade Proposal
The important engineering boundary remains:
Data
↓
Analysis
↓
Trade Intent
↓
Risk
↓
Execution
Portfolio-aware execution
A simple trading bot might know only:
symbol
price
signal
A portfolio-aware agent can incorporate:
current positions
buying power
existing exposure
trade history
P&L
For example:
Agent:
"Buy $500 of XYZ."
Portfolio:
Existing XYZ = $2,200
Risk:
Maximum XYZ position = $2,500
Result:
REJECT
The agent does not need to be trusted to remember the limit.
The policy engine enforces it.
Example workflow
Here is an example from start to finish:
User
|
| "Find a momentum opportunity
| and propose a $500 trade."
|
v
AI Agent
|
| market-data tools
v
Robinhood MCP
|
v
Trade Intent
|
v
Risk Gateway
|
+--> symbol check
+--> order-size check
+--> position check
+--> portfolio check
|
v
Approval
|
v
Order
|
v
Robinhood
|
v
Execution State
|
v
Audit Log
This is the architecture I want the GitHub project to demonstrate.
TypeScript service boundary
The core service can have one main operation:
async function processTradeIntent(
intent: TradeIntent,
): Promise<TradeDecision> {
validateTradeIntent(intent);
const riskDecision = riskEngine.evaluate(intent);
if (!riskDecision.allowed) {
return {
status: "REJECTED",
riskDecision,
};
}
return approvalService.handle(intent);
}
The important part is that the AI model does not call the broker directly.
It submits a typed request into the application's controlled pipeline.
Testing risk rules
Risk logic should be highly testable.
For example:
max order = $1,000
$500 → allowed
$1,000 → allowed
$1,001 → rejected
Position limits:
current = $2,000
limit = $2,500
order = $400 → allowed
order = $500 → allowed
order = $501 → rejected
Symbol restrictions:
AAPL → allowed
XYZ → rejected
Daily loss:
daily loss = $490
limit = $500
new trade allowed if other constraints pass
Tests should cover boundary values.
Testing permissions
Permission tests should verify that an agent cannot use tools it does not have.
For example:
Research Agent
↓
get_portfolio
↓
ALLOW
But:
Research Agent
↓
place_order
↓
DENY
This gives the permission layer a simple and deterministic contract.
Testing approval flows
Test both modes.
Assisted:
Trade
↓
Risk passes
↓
Approval pending
↓
User approves
↓
Execution
Rejected:
Trade
↓
Risk passes
↓
Approval pending
↓
User rejects
↓
No execution
Automated:
Trade
↓
Risk passes
↓
Policy permits automation
↓
Execution
These tests should be independent of the actual AI model.
Testing failure paths
The system should also handle:
MCP unavailable
tool timeout
invalid tool result
risk rejection
approval timeout
order rejected
order canceled
execution failure
state synchronization failure
The goal is not to make the AI appear perfect.
The goal is to make the surrounding system behave predictably when the AI or external infrastructure is imperfect.
Secrets and account security
The gateway must not become a place where secrets are casually stored.
Do not:
log credentials
commit .env files
store private secrets in audit records
print authentication tokens
Use environment variables or an appropriate secret-management layer.
The repository should contain:
.env.example
but never actual credentials.
Why this architecture is reusable
The risk gateway does not have to be limited to one strategy.
It can sit underneath:
Momentum Agent
↓
Risk Gateway
Rebalancing Agent
↓
Risk Gateway
Portfolio Agent
↓
Risk Gateway
Research + Execution Agent
↓
Risk Gateway
The agent changes.
The controls remain.
The bigger architecture
The project fits into a larger trading system:
USER
|
v
AI AGENT
|
v
ROBINHOOD TRADING MCP
|
v
+--------------------+
| Permission Layer |
+----------+---------+
|
v
+--------------------+
| Trade Intent |
| Validation |
+----------+---------+
|
v
+--------------------+
| Risk Gateway |
+----------+---------+
|
v
+--------------------+
| Approval / Policy |
+----------+---------+
|
v
+--------------------+
| Robinhood Order |
+----------+---------+
|
v
ORDER STATE
|
v
AUDIT LOG
The gateway is the control point.
Why this is different from a traditional trading bot
A traditional bot often looks like:
Signal
↓
Order
A controlled agentic trading system looks more like:
User Intent
↓
AI Agent
↓
Tools
↓
Structured Intent
↓
Permission
↓
Risk
↓
Approval / Policy
↓
Execution
↓
Audit
↓
Reconciliation
That is a fundamentally different engineering problem.
What this proves to a client
The valuable statement is not:
"I can connect ChatGPT to Robinhood."
The stronger statement is:
I can build the controlled infrastructure around an AI trading agent.
That includes:
AI integration
+
MCP
+
Tool permissions
+
Structured outputs
+
Risk controls
+
Approval workflows
+
Execution
+
Auditability
+
State management
+
Failure handling
That is much closer to what a real trading product requires.
Final architecture
ROBINHOOD AI AGENT
|
v
TRADING MCP
|
v
TOOL PERMISSIONS
|
v
STRUCTURED INTENT
|
v
RISK ENGINE
|
+---------+---------+
| |
v v
REJECT APPROVE
|
v
TRADE APPROVAL
|
v
ROBINHOOD
|
v
ORDER STATE
|
v
AUDIT LOG
|
v
RECONCILIATION
The core principle is simple:
AI proposes. Software validates. Policy controls. Robinhood executes.
That is the architecture I am using to explore the new generation of Robinhood agentic trading infrastructure.
Top comments (0)