内容
# robinhood-mcp
官方Robinhood加密交易API的执行工具包,通过模型上下文协议(Model Context Protocol)交付。
Robinhood的加密API仅暴露四种订单类型:`市价单`、`限价单`、`止损单`和`止损限价单`。没有`一篮子订单`、`一触即止`、`追踪止损`、`TWAP`、`冰山订单`、`分批进场`、`定时DCA`、`投资组合再平衡`等订单类型。这些是人们实际想要的订单类型,每一种都有相同的要求:必须在请求返回后继续工作。仅在一次工具调用期间存在的追踪止损并不是真正的追踪止损。本软件包合成Robinhood缺乏的十三类订单类型,并作为**持久的后台作业**运行,这些作业可以承受代理断开连接、服务器重启和机器重启。在此基础上,提供了约73种工具,涵盖8个可选模块:行情、持仓、先进的成本基准和已实现盈亏、交易日记、风险限制和强制杀死开关,以及集成诊断。
> **这不是Robinhood Chain,也不是Robinhood股票交易**。它针对的是`trading.robinhood.com`上的经纪加密交易API,详见[docs.robinhood.com/crypto/trading](https://docs.robinhood.com/crypto/trading/)。
---
## 签名测试平台 - [nirholas.github.io/robinhood-mcp](https://nirholas.github.io/robinhood-mcp/)
该项目站点是一个交互式的**Ed25519签名测试平台**。您可以组装一个真实的Robinhood交易请求,并在浏览器中生成一个真实的签名 - 数学运算在客户端运行,使用Robinhood发布的示例密钥,完全不会发送任何数据。然后,您可以验证签名是否与Robinhood自己的文档不符:他们发布的测试向量与Python `dict`表示的返回值不符,而不是每个客户端实际发送的JSON正文。这是一个自包含的单页应用,位于[`docs/`](docs/)中,因此可以部署在任何静态网站上。
您可以一键部署:
[](https://deploy.workers.cloudflare.com/?url=https://github.com/nirholas/robinhood-mcp)
[](https://app.netlify.com/start/deploy?repository=https://github.com/nirholas/robinhood-mcp)
[](https://vercel.com/new/clone?repository-url=https://github.com/nirholas/robinhood-mcp&project-name=robinhood-mcp&output-directory=docs)
| 目标 | 方法 |
| --- | --- |
| **GitHub Pages** | 设置 → Pages → 源:`main` / `/docs`。在`nirholas.github.io/robinhood-mcp/`上提供服务。无需构建步骤。 |
| **Cloudflare Pages** | 上方的按钮,或`wrangler pages deploy docs`。在[`wrangler.toml`](wrangler.toml)中配置。 |
| **Google Cloud** | `gcloud storage cp -r docs/* gs://<bucket>/` 在HTTPS负载均衡器后面,或使用任意静态服务器根目录位于`docs/`的Cloud Run。 |
| **Netlify / Vercel** | 上方的按钮。发布目录是`docs/`([`netlify.toml`](netlify.toml),[`vercel.json`](vercel.json))。 |
---
## 安全
**Robinhood没有发布沙盒环境**。没有模拟账户,没有测试环境,没有 dry-run 主机。此软件包可以下达的每个订单都是真实货币,存入真实的经纪账户,并且没有办法进行演练。在设置`ROBINHOOD_CRYPTO_ENABLE_TRADING=1`之前,请阅读本节。
以下每个防护措施都在**代码中**强制执行,在所有订单都经过的单个`Executor`中执行,而不是在工具描述中。提示中的指令是建议,而抛出的错误不是。
| 防护措施 | 功能 |
| --- | --- |
| **独立二进制文件** | `robinhood-mcp`数据服务器注册的工具不能交易。将其附加到通用助手不能导致下单。 |
| **显式启用** | 交易服务器和守护进程除非`ROBINHOOD_CRYPTO_ENABLE_TRADING=1`,否则拒绝启动。不能意外启动。 |
| **每订单美元上限** | 每个订单在提交前都会定价,如果超过`ROBINHOOD_CRYPTO_MAX_ORDER_USD`(默认为$100),则拒绝。**失败关闭**:如果无法确定美元价值,则订单被拒绝而不是假设便宜。 |
| **累计美元上限** | `ROBINHOOD_CRYPTO_MAX_DAILY_USD` 在进程累计达到该金额后拒绝订单。 |
| **符号允许列表** | 设置`ROBINHOOD_CRYPTO_SYMBOL_ALLOWLIST`后,仅允许交易该列表中的货币对。其他一律拒绝。 |
| **仅买入** | `ROBINHOOD_CRYPTO_BUY_ONLY=1` 拒绝所有卖出订单。 |
| **受控 vs 自动** | 默认`受控`模式下,订单工具返回实际订单请求和价格估算,除非第二个调用带有`confirm: true`,否则不会下单。`ROBINHOOD_CRYPTO_AUTONOMOUS=1` 跳过此检查,适用于无人值守的策略。 |
| **杀死开关** | `risk_kill_switch_engage` 停止所有执行并暂停正在运行的作业。持久化到磁盘,每次订单都读取,不会缓存,在`Executor`中检查。 |
关于这些控制措施,有两点需要明确说明,因为人们经常理解有误。
**自主模式不会移除人类,而是移除上限**。每订单上限、累计上限、允许列表和仅买入标志在自主模式下仍然适用。无人值守的代理是运行失控订单最可能且最不被察觉的场景。自控开关绑定到守护进程**。它与`Executor`共存,而不是位于风险工具模块中,因为`robinhood-mcp-daemon`会构建自己的`Executor`,并且根本不加载工具模块。模块中安装的暂停不会留下仍在下单的无人值守进程。因为状态每次订单都从磁盘读取,而不是缓存,所以从MCP会话中触发开关会停止一个可能在开关存在之前已经运行的守护进程。失败关闭:无法解析的行读取为*已启用*,因为恢复交易可能会导致损坏,而这是杀死开关存在的目的。
两个限制是对其不能提供的功能进行诚实说明。累计上限是一个**进程内、内存中**的计数器,在重启时重置:它是一个防止失控循环的刹车,而不是会计系统或硬预算。**持久作业的总花费不受限制**。每订单上限适用于作业下的每个订单,但具有50个切片的TWAP作业在每订单上限为$100的情况下可以提交$5000。请谨慎规划作业并在无人值守的情况下检查`risk_status`。
最后,请限制API密钥本身的范围。Robinhood允许您按凭证设置权限:如果您只需要读取权限,请不要授予订单放置权限,并且无论哪个软件持有该密钥,密钥都无法交易。还可以使用IP允许列表,值得使用。
---
## 安装
需要Node.js **22.5或更新版本**(持久作业存储使用`node:sqlite`)。
无需预先安装:MCP客户端使用`npx`启动服务器。
### 1. 创建凭证
```bash
npx robinhood-keygen
```
这会以JSON格式在stdout上打印一对新的Ed25519密钥对,并附有stderr上的说明。然后:
1. 在[robinhood.com/account/crypto](https://robinhood.com/account/crypto)上注册**公钥**。此页面仅在**经典网页**上存在,不在移动应用或新网页UI上。
2. Robinhood会颁发一个API密钥,即`ROBINHOOD_CRYPTO_API_KEY`。
3. 将私钥保存为`ROBINHOOD_CRYPTO_PRIVATE_KEY`。它是**base64编码的32字节Ed25519种子**,不是64字节的扩展密钥对,也不是PKCS#8。几个Ed25519库导出`seed‖publicKey`;粘贴它会得到64个字节,并使每个请求失败。请像对待密码一样对待私钥:它授权交易,并且此软件包从不存储或传输它,只在本地计算签名。
如果您已经拥有其他来源的密钥对,`verify_keypair`(在`dev`模块中)检查私钥是否与您注册的公钥匹配,而无需发送任何数据。
### 2. 将服务器添加到MCP客户端
该软件包中附带两个二进制文件,它们是分开的。
| 服务器 | 二进制文件 | 功能 |
| --- | --- | --- |
| 数据 | `robinhood-mcp` | 行情、持仓、账户、订单历史、成本基准、盈亏。**不能下单**。 |
| 交易 | `robinhood-mcp-trading` | 以上所有功能,以及真实订单、综合订单类型和风险控制。 |
**只读**。这是首先启动的服务器,可以附加到通用助手。在`claude_desktop_config.json`(Claude Desktop)、`.mcp.json`(Claude Code)或客户端的等效文件中粘贴:
```json
{
"mcpServers": {
"robinhood-crypto": {
"command": "npx",
"args": ["-y", "robinhood-mcp"],
"env": {
"ROBINHOOD_CRYPTO_API_KEY": "rh-api-...",
"ROBINHOOD_CRYPTO_PRIVATE_KEY": "your-base64-seed"
}
}
}
}
```
询问您的助手: *"我的Robinhood加密购买力是多少,BTC正在交易多少?"*
**交易**。一个单独的入口,因为它会下真实订单:
```json
{
"mcpServers": {
"robinhood-crypto-trading": {
"command": "npx",
"args": ["-y", "robinhood-mcp-trading"],
"env": {
"ROBINHOOD_CRYPTO_API_KEY": "rh-api-...",
"ROBINHOOD_CRYPTO_PRIVATE_KEY": "your-base64-seed",
"ROBINHOOD_CRYPTO_ENABLE_TRADING": "1",
"ROBINHOOD_CRYPTO_MAX_ORDER_USD": "50",
"ROBINHOOD_CRYPTO_MAX_DAILY_USD": "500",
"ROBINHOOD_CRYPTO_SYMBOL_ALLOWLIST": "BTC-USD,ETH-USD"
}
}
}
}
```
将上限设置为您愿意损失的金额。它们是唯一能防止误解指令导致实际成交的保障。
您可以同时运行这两个服务器。数据服务器可以安全地永久附加;当您打算交易时添加交易服务器。
### 如果您尚未设置凭证
数据服务器仍然会启动。除了退出(大多数MCP客户端仅显示为不透明的“服务器启动失败”),它会以一个`get_setup_status`工具启动,该工具报告缺少的内容以及如何修复它。如果这是您看到的唯一工具,则表示凭证未配置:设置`ROBINHOOD_CRYPTO_API_KEY`和`ROBINHOOD_CRYPTO_PRIVATE_KEY`,然后重启服务器,完整的工具集就会出现。
交易服务器故意更严格,如果没有`ROBINHOOD_CRYPTO_ENABLE_TRADING=1`,则拒绝启动,因此它永远不会被意外启动。
---
## 模块
工具包比任何代理一次看到的都要大。每个工具都会消耗上下文并添加错误的选择,这会明显降低工具的选择,因此功能作为**模块**提供,由操作员使用`ROBINHOOD_MCP_MODULES`启用。只有选定的模块才会被注册。
| 模块 | 功能 | 默认 | 下单 |
| --- | --- | --- | --- |
| `market` | 行情、持仓、账户详情、交易对、订单历史。 | 开启 | 否 |
| `orders` | 四种本地订单类型作为单独的工具,以及通用`place_order`和`cancel_order`。 | 开启 | **是** |
| `algo` | 十三种综合订单类型,作为持久后台作业运行。 | 开启 | **是** |
| `portfolio` | FIFO成本基准、已实现盈亏和税务批次、配置、滑点、波动率、基于风险的 sizing。 | 开启 | 否 |
| `risk` | 暴露、集中度、回撤、交易前检查和强制杀死开关。 | 开启 | **是** |
| `journal` | 成交量、往返交易、胜率和利润因子、每日总结、过时开仓订单、CSV导出、核对。 | 关闭 | 否 |
| `ops` | 健康检查、时钟偏差、密钥验证、速率限制状态、API版本、主管和未结算意图状态。 | 关闭 | 否 |
| `dev` | 连接诊断、签名解释、密钥对生成和验证、客户端代码生成、环境检查。 | 关闭 | 否 |
`risk`被归类为下单模块,因为启用kill switch会暂停正在运行的作业。它本身不能提交订单。
**如何选择:**
- **未设置** 加载默认设置(`market`、`orders`、`algo`、`portfolio`、`risk`),减去服务器无法承载的模块。对于交易服务器,这是52种工具;对于数据服务器,这是16种工具。
- **逗号分隔的列表** 加载确切的列表:`ROBINHOOD_MCP_MODULES=market,orders,risk`。
- **`all`** 加载所有功能:交易服务器上有73种工具。
未知模块名称**导致启动失败**,而不是被静默删除,因为一个悄悄删除工具的输入错误比服务器拒绝启动更糟糕。
**加载所有功能会降低工具选择质量**。`all`对于构建者探索界面非常有用,但对于生产代理来说是一个糟糕的选择。请挑选任务实际需要的模块。如果代理报告某个功能无法实现,请调用`list_modules`:该工具会提供确切的`ROBINHOOD_MCP_MODULES`值,以启用缺失的功能,因此不会将禁用的模块与工具包缺乏的功能混淆。
数据服务器拒绝加载变更模块(`orders`、`algo`、`risk`)并在启动时失败,如果被要求这样做,因此操作员可以确定何时启用执行。
所有73种工具的参数完整参考:**[docs/tools.md](docs/tools.md)**。
## The thirteen synthetic order types
Each of these is built on top of Robinhood's four primitives by a supervisor that keeps advancing it over time. Every one is started by its own tool with a typed schema, so a missing or misnamed parameter fails at the schema boundary instead of inside the job.
| Order type | Tool | What it does | Key parameters |
| --- | --- | --- | --- |
| **TWAP** | `algo_twap_start` | Splits one large order into evenly spaced slices over a duration, so the entry averages across the window instead of paying a single spread. | `total_quantity`, `slices`, `duration_minutes`, optional `limit_price` guard |
| **Trailing stop** | `algo_trailing_stop_start` | Follows the market with a stop that ratchets in your favour and never retreats, exiting the full size when price retraces past the trail. | `quantity`, exactly one of `trail_percent` / `trail_amount`, optional `activation_price` |
| **Bracket** | `algo_bracket_start` | Enters a position and attaches a take-profit and a stop-loss that cancel one another, so the trade is never left with only one exit. | `entry_type`, `entry_price`, `take_profit_price`, `stop_loss_price` |
| **OCO** | `algo_oco_start` | Rests a take-profit and a stop against an **existing** position and cancels whichever does not fill. | `quantity`, `take_profit_price`, `stop_price`, optional `stop_limit_price` |
| **DCA** | `algo_dca_start` | Buys or sells a fixed amount on a repeating schedule for a set number of occurrences. | `interval_hours`, `occurrences`, one of `quote_amount_per_buy` / `asset_quantity_per_buy`, optional `max_price` |
| **Ladder** | `algo_ladder_start` | Places a series of resting limit orders across a price range, to scale into or out of a position as price moves. | `total_quantity`, `levels`, `start_price`, `end_price`, `distribution` |
| **Iceberg** | `algo_iceberg_start` | Works a large order behind a small visible slice, refilling as each slice fills, so the book never shows the full size. | `total_quantity`, `visible_quantity`, `max_duration_minutes`, optional `limit_price` |
| **Chase** | `algo_chase_start` | Rests a limit order and reposts it as the book moves away, to fill without crossing the spread. | `quantity`, `max_chases`, `offset_bps`, optional `limit_price` bound |
| **Grid** | `algo_grid_start` | Rests buys below and sells above across a price range, replacing each fill with its opposite one level away, banking the spacing on every round trip. | `lower_price`, `upper_price`, `grid_levels`, `quantity_per_grid`, optional `max_cycles` / `max_duration_minutes` / `stop_price` |
| **Momentum** | `algo_momentum_start` | Enters when price breaks out of its recent range by a set margin, then exits when it retraces a set fraction of the move it reached. | `side`, `quantity`, `lookback_ticks`, `breakout_pct`, `exit_pct`, `max_duration_minutes` |
| **Mean reversion** | `algo_mean_reversion_start` | Fades a price that has stretched a set number of standard deviations from its recent mean, and closes the trade when it reverts toward that mean. | `quantity`, `lookback_ticks`, `entry_z`, `exit_z`, `side_mode`, `max_duration_minutes` |
| **Accumulate** | `algo_accumulate_start` | Buys toward a target size only on weakness, taking a slice whenever price trades a set percentage below its recent average and under a hard ceiling. | `target_quantity`, `slice_quantity`, `max_price`, `buy_below_pct`, `lookback_ticks`, `max_duration_minutes` |
| **Rebalance** | `algo_rebalance_start` | Drives holdings toward target weights, selling what is overweight and buying what is underweight, sells before buys. | `targets`, `tolerance_bps`, `max_legs_per_tick`, `dry_run` |
Each tool's description states its own failure modes, which are not the same across strategies. A TWAP with a `limit_price` can finish short of the full size. An OCO has unavoidable exposure between one leg filling and the other being cancelled, and reports a double fill as `failed` rather than as success. An iceberg that hits `max_duration_minutes` cancels its working slice and completes under-filled. Read the tool description before running one for real, and use `algo_rebalance_start`'s `dry_run` to see the legs before any of them are sent.
**This API is spot only, which constrains four of these.** There is no borrow and no short position to open, so nothing here shorts:
- **`algo_mean_reversion_start`** with `side_mode` of `short_only` or `both` does not open a short. It sells an existing holding into an extreme high and buys it back on reversion. Both modes are rejected at start unless the account already holds enough to cover `quantity`, and every short entry re-checks the balance before submitting.
- **`algo_momentum_start`** with `side: "sell"` is the same shape: a momentum exit from coins the account already holds, not a short. `init` refuses the job if the holding does not exist.
- **`algo_grid_start`** will not sell coins its own buys have not funded. `max_cycles` and `max_duration_minutes` always carry a value (defaults: 100 cycles, 7 days), because a grid with neither never terminates. `stop_price` is the only path that liquidates: it cancels the resting orders and market-sells grid inventory.
- **`algo_accumulate_start`** waits for dips rather than buying on a clock as `algo_dca_start` does, so it **may finish short of target** if no qualifying dip arrives before `max_duration_minutes`. A run that ends short is reported as short.
---
## Durable jobs
A start tool does **not** execute an algorithm inside the call. It writes a durable job and returns; a supervisor owns execution from then on.
```
algo_twap_start ("TWAP 0.5 BTC over 2 hours")
|
v
JobStore (SQLite) <--> Supervisor --> Strategy.advance() --> Executor
(tick loop)
```
That is the whole point: **the job outlives the conversation.** It survives the agent disconnecting, the MCP server restarting, and the machine rebooting. It also keeps spending money after the chat window is closed, which is why every start tool says so and why `algo_cancel_job` exists.
Jobs live in a SQLite database at `~/.robinhood-mcp/jobs.db`, overridable with `ROBINHOOD_MCP_DB`. It is under the home directory rather than the working directory so a job survives being started from a different folder and is not accidentally committed to a repo. The kill-switch state lives in the same file, so the switch and the jobs it halts can never disagree about which database is authoritative.
### Unattended execution
An MCP server only runs while a client holds it open, so a job started through MCP advances only while that conversation is alive. For unattended execution run the daemon, which owns the same job database:
```bash
ROBINHOOD_CRYPTO_ENABLE_TRADING=1 \
ROBINHOOD_CRYPTO_MAX_ORDER_USD=50 \
npx robinhood-mcp-daemon
```
Run it under something that restarts it: systemd, launchd, pm2, a container. Restarts are safe by design. Credentials stay on the machine; the daemon talks only to Robinhood.
If jobs exist and look healthy but nothing is happening, the supervisor is not ticking. `ops_supervisor_status` is the check for that failure, which produces no error at all. A TWAP that is not ticking is not slow, it is stopped.
### Crash safety
Getting this wrong means double-filling a real order with real money.
The naive design submits an order and records it afterwards. If the process dies between those two steps, the order exists at Robinhood and the job has no memory of it; on restart the strategy repeats the slice and the user is filled twice.
Instead every order is a three-phase write:
1. **Reserve.** Generate the `client_order_id` and persist an intent row *before* any network call.
2. **Submit.** POST with that exact id, with retries disabled, since retrying an ambiguous write can double-fill.
3. **Settle.** Record the response.
On startup the supervisor reconciles every still-pending intent against Robinhood by `client_order_id`. **Found** means the order was placed before the crash, so it is adopted and never resubmitted. **Conclusively not found** means Robinhood never saw it. If the lookup itself did not complete, the intent stays pending and is retried, because an inconclusive answer must never be read as "not found": Robinhood offers no `client_order_id` query filter, so the lookup is a bounded walk of order history, and abandoning an intent whose order is live would re-place that slice under a fresh id.
After any crash, `ops_jobs_pending_intents` lists what was left unsettled and `journal_reconcile` compares the whole local record against Robinhood. Do not resubmit those by hand: reconciliation adopts the order if it exists, and a manual resubmit under a new id is how one intended fill becomes two real ones.
---
## Configuration
| Variable | Required | Default | Purpose |
| --- | --- | --- | --- |
| `ROBINHOOD_CRYPTO_API_KEY` | yes | none | API key issued by Robinhood when you register your public key. |
| `ROBINHOOD_CRYPTO_PRIVATE_KEY` | yes | none | Base64-encoded **32-byte Ed25519 seed**. Never a 64-byte expanded key, never PKCS#8. |
| `ROBINHOOD_CRYPTO_API_VERSION` | no | `v1` | `v1` or `v2`. v2 adds fee-tier data. Any other value fails startup. |
| `ROBINHOOD_CRYPTO_BASE_URL` | no | `https://trading.robinhood.com` | Override the API host. There is no sandbox host to point this at. |
| `ROBINHOOD_MCP_MODULES` | no | the default set | Comma-separated module names, or `all`. An unknown name fails startup. |
| `ROBINHOOD_MCP_DB` | no | `~/.robinhood-mcp/jobs.db` | Where durable jobs and the kill-switch state live. |
| `ROBINHOOD_MCP_TICK_MS` | no | `5000` | Daemon supervisor tick interval, in milliseconds. |
| `ROBINHOOD_CRYPTO_ENABLE_TRADING` | trading server and daemon | `0` | Must be exactly `1` for either to start. Nothing else enables trading. |
| `ROBINHOOD_CRYPTO_AUTONOMOUS` | no | `0` | `1` executes immediately and ignores `confirm`. Spend caps still apply. |
| `ROBINHOOD_CRYPTO_MAX_ORDER_USD` | no | `100` | Hard per-order ceiling. Applies in every mode. Must be a positive number. |
| `ROBINHOOD_CRYPTO_MAX_DAILY_USD` | no | unset (no cap) | Cumulative per-process ceiling. In-memory; resets on restart. |
| `ROBINHOOD_CRYPTO_SYMBOL_ALLOWLIST` | no | unset (all pairs) | Comma-separated pairs, e.g. `BTC-USD,ETH-USD`. When set, only these may trade. |
| `ROBINHOOD_CRYPTO_BUY_ONLY` | no | `0` | `1` rejects every sell order. |
See [`.env.example`](.env.example) for a copy-paste starting point. `check_environment` (in the `dev` module) reports which of these are set and what each currently does, without printing any secret. `risk_limits_export` (in the `risk` module) dumps every enforced limit with the variable that sets it.
### v1 vs v2
v2 adds fee-tier data but is not a drop-in replacement. Its `holdings`, `orders`, and order-placement endpoints require an `account_number` query parameter (resolved automatically here), `estimated_price` moves from `/marketdata/` to `/trading/`, and v2 reports order state `pending` where v1 reports `partially_filled`. Code written against one version can fail against the other for any of those reasons. Default is `v1`; set `v2` if you want fee data. `ops_api_version` explains the differences at runtime.
---
## Examples
### Price a trade, then place it
In guarded mode an order tool previews first. A typical exchange:
1. `get_best_bid_ask` with `symbols: ["BTC-USD"]` to see the live market.
2. `get_trading_pairs` with `symbols: ["BTC-USD"]` to check the quantity increment, because a quantity with too many decimals is rejected.
3. `risk_check_order` to test the order against every control without placing it. It returns a plain allowed true or false and names the control that would reject it.
4. `buy_limit` with `symbol: "BTC-USD"`, `limit_price: "60000.00"`, `quote_amount: "25.00"`. Without `confirm` this returns the exact request that would be sent plus a priced estimate, and places nothing.
5. `buy_limit` again with the same arguments plus `confirm: true`. Now it sends.
6. `get_order` with the returned order id to confirm the fill.
Step 3 matters most for multi-order plans: it fails on paper instead of halfway through with real money already spent.
### Accumulate over two hours, then protect the position
Split the entry so it averages across the window rather than paying one spread:
- `algo_twap_start` with `symbol: "BTC-USD"`, `side: "buy"`, `total_quantity: "0.05"`, `slices: 12`, `duration_minutes: 120`.
That returns a job id and returns immediately. Track it with `algo_list_jobs`, or `algo_get_job` for the full event history and every order it has placed. Stop it early with `algo_cancel_job`, remembering that this stops *further* orders and does not cancel ones already resting: `algo_get_job` lists those by id for `cancel_order`.
Once filled, protect the position with an OCO exit pair:
- `algo_oco_start` with `symbol: "BTC-USD"`, `side: "sell"`, `quantity: "0.05"`, `take_profit_price: "72000.00"`, `stop_price: "58000.00"`.
Both of these outlive the conversation, so run `robinhood-mcp-daemon` if you want them to keep advancing after you close the chat.
### Stop everything
If something looks wrong and you would rather stop than work out why:
- `risk_kill_switch_engage` with a `reason`. Every order is refused from the moment it returns, every running job is paused, and the halt survives a restart of both the server and the daemon.
- `cancel_order` for anything already resting at Robinhood. The kill switch does not cancel those.
- `risk_status` to see the full picture, then `risk_kill_switch_release` with `confirm: true` when the cause no longer applies.
---
## Library use
The signed client works without MCP:
```javascript
import { RobinhoodCryptoClient, loadCredentials } from 'robinhood-mcp';
const client = new RobinhoodCryptoClient(loadCredentials());
const quote = await client.get('/api/v1/crypto/marketdata/best_bid_ask/', {
query: { symbol: ['BTC-USD'] },
});
console.log(quote);
```
Responses are returned exactly as Robinhood sends them. Field names are deliberately **not** remapped: the published schemas and the community clients disagree in places, and a wrong mapping corrupts data silently while a pass-through stays correct even as the upstream shape changes.
---
## Authentication, and two things that will bite you
Every request carries `x-api-key`, `x-timestamp`, and `x-signature`, where the signature is a detached Ed25519 signature over:
```
message = api_key + timestamp + path + method + body
```
concatenated with no separators. Three details matter:
- **The private key is a 32-byte seed**, not a 64-byte expanded keypair and not PKCS#8. Several Ed25519 libraries export `seed‖publicKey`; pasting that gives 64 bytes and fails. This package detects that case by name.
- **Timestamps are Unix seconds and expire after 30 seconds.** `Date.now()` returns milliseconds; using it directly fails every request. Keep the system clock synced, and use `ops_clock_skew` when a 401 looks like a bad key but might be a drifting clock.
- **The path includes the query string and the trailing slash.** Both are signed. Serialize the body once, sign those exact bytes, send those exact bytes: re-serializing changes key order or whitespace and produces a well-formed request with an invalid signature.
When a signature is rejected, `explain_signature` (in the `dev` module) shows the exact bytes being signed so another implementation can be compared byte for byte, and `diagnose_connection` runs every check that distinguishes the common causes of a 401.
### Robinhood's published conformance vector is wrong
The docs publish a worked example and invite you to check your signature against it. **That vector does not correspond to the JSON body a real client sends.** It was generated by signing a Python `dict` repr: single quotes, spaces after colons, and a different key order than the accompanying table shows:
```
{'client_order_id': '...', 'side': 'buy', 'symbol': 'BTC-USD', 'type': 'market', ...}
```
Signing the actual JSON body, which is what every working client and Robinhood's own sample code does at runtime, will *not* reproduce the published signature. Anyone using that vector as a conformance check will "fix" a correct implementation into a broken one.
Both facts are pinned in [`tests/signer.test.ts`](tests/signer.test.ts) so the quirk stays documented. The signer is verified the sound way instead: Robinhood publishes both halves of an example keypair, and this implementation derives their exact public key from their private key, then round-trips a signature through independent verification.
---
## 它不具备的功能
- **不支持股票或期权交易**。这是加密货币API。Robinhood的代理交易产品位于 `agent.robinhood.com/mcp/trading`,是一个单独的、仅限股票的界面,具有自己的OAuth流程,不包含在此封装中。目前没有计划添加股票或期权交易。
- **不通过私用API抓取数据**。几个社区服务器通过浏览器会话或未经身份验证的内部API驱动Robinhood。此软件包仅使用文档化的、签名验证的、官方支持的API。
- **没有沙盒环境,因为不存在**。没有可供回落的模拟模式,也无法从Robinhood外部添加。`ROBINHOOD_CRYPTO_BASE_URL`可以覆盖主机,但没有测试主机可供指向。上述防护措施是替代方案,也是唯一可用的方案。
- **没有历史蜡烛图和订单簿深度**。Robinhood不发布这些数据。因此,`get_volatility` 需要您提供价格序列,而不是获取它,而 `estimate_slippage` 仅模拟报价价差,这是真实成本的下限,而不是模拟。
- **没有跨交易所视图**。每个派生数字(成本基数、已实现盈亏、回撤、交易日记性能)仅根据Robinhood订单历史重新构建。存款、取款、从其他交易所转账、质押奖励和空投对其不可见,受影响的工具在其描述中说明。
- **没有供应商提供的OpenAPI规范**。 [`scripts/fetch-spec.mjs`](scripts/fetch-spec.mjs) 实时获取它,因为供应商提供的副本会无声无息地过期。
---
## 开发
```bash
npm install
npm test # 运行单元测试和集成测试,无需凭证或网络连接
npm run typecheck
npm run build
npm run smoke # 启动已构建的服务器,通过标准输入输出列表工具
npm run test:live # 访问真实API;需要凭证;只读
```
`npm test` 永远不会接触网络:策略数学、策略执行和对账测试是针对内存中的真实SQLite存储进行的,网络边界是唯一被替代的东西。`npm run smoke` 执行真实MCP客户端执行的握手,这可以捕获单元测试无法捕获的问题:过时的 `dist/`、错误的二进制shebang或注册期间抛出的模块。实时测试套件是可选且只读的,不会下订单。
设计原理、分层和代码添加位置:[`docs/architecture.md`](docs/architecture.md)。
## 许可证
Apache-2.0。查看 [LICENSE](LICENSE)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器