arcus-agent-gateway
arcus-agent-gateway
An MCP (Model Context Protocol) server that gives AI agents read-only, keyless
access to market data for the 194 tokenized US equities on Robinhood Chain
(Arcus) — quotes, corporate actions, trading capabilities, multipliers and a
13-sector map. No API keys, no auth, no writes: every tool is a GET against the
public api.robinhood.com/rhj REST surface, cached and rate-limited so an
enthusiastic agent can't hammer the upstream.
Use cases
"Who actually holds AAPL?" — top holders with on-chain share %, contract vs EOA, concentration risk (holder_snapshot scenario)
Watch any wallet — full portfolio across all 194 tokenized equities, valued at cached quotes (
wallet_holdings)Catch whale moves — live ERC-20 Transfer feed with a
min_valuefilter for large-print alerts (transfer_history)Split-safe prices — raw vs multiplier-adjusted quotes side by side, pending-split warnings with effective time (
quote,token_detail)Morning scan — market-wide health, halted tokens and 13-sector averages in two cheap calls (
market_status,sector_view(warm=True))
Full walkthroughs with real outputs: examples/use-cases.md.
Quickstart
Run over stdio (the default, for local agents):
uvx arcus-agent-gatewayStandard config for Claude Desktop / Cursor (claude_desktop_config.json / .cursor/mcp.json):
{
"mcpServers": {
"arcus": {
"command": "uvx",
"args": ["arcus-agent-gateway"]
}
}
}[mcp_servers.arcus]
command = "uvx"
args = ["arcus-agent-gateway"]# 1) start the gateway (keep it running)
uvx arcus-agent-gateway --http --port 8902 &
# 2) register it (merges into ~/.zcode/cli/config.json; workspace .zcode/config.json works too)
python3 - <<'PY'
import json, os
p = os.path.expanduser("~/.zcode/cli/config.json")
os.makedirs(os.path.dirname(p), exist_ok=True)
cfg = json.load(open(p)) if os.path.exists(p) else {}
cfg.setdefault("mcp", {}).setdefault("servers", {})["arcus"] = {
"type": "http", "url": "http://127.0.0.1:8902/mcp"}
json.dump(cfg, open(p, "w"), indent=2)
print("arcus MCP server registered:", p)
PY
# 3) copy the agent skill (tool guide + watchlist cron recipe)
git clone -q --depth 1 https://github.com/alekskram/arcus-agent-gateway /tmp/aag
cp -r /tmp/aag/.agents/skills/arcus-gateway ~/.zcode/skills/ && rm -rf /tmp/aag
echo "ZCode setup done — restart your session and call any arcus tool"Hosted form — streamable HTTP on port 8902:
uvx arcus-agent-gateway --http # 127.0.0.1:8902
curl http://127.0.0.1:8902/health # -> {"ok": true, "service": "arcus-agent-gateway"}Tools
All 13 tools are read-only (annotated readOnlyHint: true). Names and
parameters are exactly as registered by arcus_mcp/server.py.
# | Tool | Signature | What it does |
1 |
|
| Tokenized equities, one row per token (symbol, name, status, multiplier, tradable); |
2 |
|
| Live quote joined with asset metadata: raw + multiplier-adjusted bid/ask/spread, |
3 |
|
| Batch of |
4 |
|
| Full dossier: contract/chain/ISIN metadata, embedded quote, last 5 corporate actions, multiplier block with history note, |
5 |
|
| Market-wide health from assets only (never fetches 194 prices): totals, untradable count, cached-halted list, extended-hours estimate. |
6 |
|
| Splits/dividends across all tokens or for one symbol; tolerant to the API's field-name variants. |
7 |
|
| Local fuzzy search over the token list; |
8 |
|
| 13-sector static map with sizes and multiplier-adjusted sector averages. Default ( |
9 |
|
| On-chain footprint joined from three independent sources (each fails to a |
10 |
|
| OHLCV history from the optional recorder's local parquet store (see below). Honest degradation: missing pyarrow or data → actionable |
11 |
|
| Top holders of a token's contract from the Blockscout explorer (one page, max 50 rows, 600 s cache). Rows: |
12 |
|
| Which of the 194 tokenized equities a wallet holds (explorer |
13 |
|
| Recent ERC-20 |
— | watchlist | — | Not a tool. Price tracking is done by your agent's scheduler (cron) calling |
Multiplier logic (read this before using prices)
Robinhood Chain tokens carry a multiplier — the corporate-action
adjustment factor for the token contract (1.0 = untouched). Splits change it;
for example NVDA's 2026-11 split queues pendingMultiplier: "4.0".
The REST API returns RAW prices.
bid/askfrom/prices/{symbol}are in token-contract units and are not multiplier-adjusted.Adjusted values are computed by this server, never taken from upstream:
price_adjusted = round(price_raw × currentMultiplier, 6).Raw and adjusted always travel together. Every quote carries
bid_raw/ask_raw/spread_rawandbid_adjusted/ask_adjusted/mid_adjustednext to themultiplierblock — never one without the other.On-chain quantities (token balances, mint/burn volumes) are natively in adjusted (multiplied) units; REST prices are not. If you compare the two, go through the
*_adjustedfields.
Worked example (live fixture, 2026-09-03):
AAPL currentMultiplier = 1.000566080061092436
bid_raw = 327.77 → bid_adjusted = round(327.77 × 1.000566…, 6) = 327.955544
ask_raw = 327.78 → ask_adjusted = 327.965550
mid mid_adjusted = 327.960547Pending split warning. When pendingMultiplier is queued (non-empty) and
differs from the current one, token_detail() adds a warning like
pending split: 1→4.0 on 2026-11-06T00:00:00Z, and quote()'s multiplier
block exposes pending + effective_time. After the split lands, raw prices
jump by the ratio while *_adjusted fields stay comparable — another reason to
always read adjusted values next to the multiplier.
API limits & caching
Upstream allows 60 req/s without a key; this client self-limits to ≤ 50 req/s (a 20 ms politeness interval between requests, thread-safe).
Transient failures (
429/502/503/504, network errors) are retried up to 3 times with2s × (attempt+1)backoff.Response caches (per process):
/assets5 min,/prices/{symbol}15 s,/corporate-actions1 h.market_status()andsector_view()are computed from caches and assets only — they never fan out 194 price requests.
On-chain sources & limits
The v0.2 on-chain tools read two keyless public sources next to the REST
API. Both are free, rate-limited and partially restricted — every tool above
degrades honestly (per-field omission + warnings[] / error dicts), never
with a silent empty answer.
Public JSON-RPC (default
robinhood-rpc.publicnode.com, override withARCUS_RPC_URL):eth_call(e.g.totalSupply()) works normally.eth_getLogsonly answers inside a floating ~45–60-block window behind the latest block — wider or older ranges get HTTP 403 "Archive requests require a personal token" (the backend is Alchemy). The window drifts minute to minute, sotransfer_history()walks back in windows that start 48 blocks wide and shrink 48→32→16→8 on each 403, capped at ~14 getLogs requests.eth_getLogslog objects carryblockTimestampdirectly — no per-block lookups are needed.Fallback RPC (
robinhood.drpc.org,ARCUS_RPC_FALLBACK_URL): has noeth_getLogsand noeth_call(JSON-RPC "method not available"); it is used only foreth_chainId/eth_blockNumber.Blockscout v2 explorer (
robinhoodchain.blockscout.com/api/v2,ARCUS_EXPLORER_URL): requires a browser User-Agent on every request — plain HTTP clients get a Cloudflare 403 "Just a moment…" HTML challenge. Token pages (holders_count,circulating_market_cap,total_supply), one holders page (max 50 rows, no pagination loops) and addresstoken-balancescome from here, cached 600 s.token-balancesanswers in ~0.5 s on plain wallets but hangs 40 s+ on huge contract addresses — the client fails honestly after 15 s with kindexplorer-timeout.On-chain activity ≠ trades. The chain records
Transfer, mint and redeem events between addresses; it knows nothing about order-book trades or prices. Usequote()/quotes()for prices andtransfer_history()for token movement.
Raw prices disclaimer
Prices are served exactly as they arrive from Robinhood (RAW) — they are
not multiplier-adjusted, and the *_adjusted fields are our computation,
not upstream data. All data is for information only, not for trading
decisions, and should be verified against the official source before you act
on it. No warranty of completeness, accuracy or timeliness.
Optional price history recorder
The Robinhood Chain REST API has no price history endpoint — only current quotes. For the 194 tokenized equities this recorder is the only history source. It is opt-in and disabled by default; nothing is recorded unless you explicitly enable it.
How it works. One tick every 5 minutes (default): fetch a quote for every
ACTIVE tradable token through the same rate-limited client (50 req/s cap;
average load ≈ 0.65 req/s), append one row per symbol to
data/history/snapshots_YYYYMM.parquet (monthly rotation), and maintain a
daily OHLCV rollup data/history/daily.parquet (open/high/low/close on
mid_adjusted, volume = max of the day's cumulative daily_volume). The
rollup runs at the first tick after midnight UTC for the previous day and is
idempotent (re-running a day overwrites it, never duplicates).
Enable it:
pip install "arcus-agent-gateway[recorder]" # adds pyarrow (optional extra)
# systemd (recommended): units ship DISABLED - enabling is your decision
sudo cp deploy/arcus-recorder.* /etc/systemd/system/
sudo systemctl enable --now arcus-recorder.timer # OnCalendar=*:0/5, Persistent
# or run one tick / a debug loop manually:
python -m arcus_mcp.recorder --once
python -m arcus_mcp.recorder --limit 5 # debug: first 5 symbols only
ARCUS_INTERVAL_SEC=60 python -m arcus_mcp.recorder # custom interval loop
# (from a git checkout, `python scripts/recorder.py ...` still works -
# it is a thin shim that delegates to arcus_mcp.recorder)Data weight & rotation. Full universe (194 symbols) at a 5-minute tick ≈
2–3 MB/day of snapshots plus ≈ 10 KB/day for the daily rollup. Snapshots
rotate monthly (snapshots_YYYYMM.parquet); delete old months when you no
longer need raw granularity — daily.parquet is the compact long-term store.
Data lands in ~/.local/state/arcus-agent-gateway/history/ (override with
ARCUS_GATEWAY_DATA).
Reading it back: the price_history tool serves daily bars and raw
snapshots from the same directory. Without pyarrow or data it returns an
actionable error pointing here — install the [recorder] extra, never a
silent empty answer.
Security & privacy
Keyless and read-only. No API keys, no auth, no writes. Every tool is annotated
readOnlyHint: true/destructiveHint: falseon the MCP wire.Rate-limited by design. Client caps at 50 req/s against the public REST surface, public RPC requests go through the same limiter, Blockscout calls carry a standard browser User-Agent and their own timeouts.
No telemetry, no logging of your prompts. The server caches public market data in memory (and parquet files only if you enable the optional recorder); nothing leaves your machine except the API reads themselves.
License
MIT — see LICENSE. Not affiliated with Robinhood Markets, Inc.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/alekskram/arcus-agent-gateway'
If you have feedback or need assistance with the MCP directory API, please join our Discord server