arcus-agent-gateway 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arcus_agent_gateway-0.1.0/.agents/skills/arcus-gateway/SKILL.md +87 -0
- arcus_agent_gateway-0.1.0/.github/workflows/release.yml +26 -0
- arcus_agent_gateway-0.1.0/.github/workflows/tests.yml +27 -0
- arcus_agent_gateway-0.1.0/.gitignore +6 -0
- arcus_agent_gateway-0.1.0/CHANGELOG.md +18 -0
- arcus_agent_gateway-0.1.0/LICENSE +21 -0
- arcus_agent_gateway-0.1.0/PKG-INFO +153 -0
- arcus_agent_gateway-0.1.0/README.md +127 -0
- arcus_agent_gateway-0.1.0/arcus_mcp/API_NOTES.md +29 -0
- arcus_agent_gateway-0.1.0/arcus_mcp/__init__.py +2 -0
- arcus_agent_gateway-0.1.0/arcus_mcp/api.py +183 -0
- arcus_agent_gateway-0.1.0/arcus_mcp/paths.py +18 -0
- arcus_agent_gateway-0.1.0/arcus_mcp/sectors.py +109 -0
- arcus_agent_gateway-0.1.0/arcus_mcp/server.py +501 -0
- arcus_agent_gateway-0.1.0/examples/use-cases.md +158 -0
- arcus_agent_gateway-0.1.0/pyproject.toml +43 -0
- arcus_agent_gateway-0.1.0/scripts/smoke_api_offline.py +198 -0
- arcus_agent_gateway-0.1.0/scripts/smoke_server_offline.py +372 -0
- arcus_agent_gateway-0.1.0/tests/conftest.py +7 -0
- arcus_agent_gateway-0.1.0/tests/fixtures/assets.json +1 -0
- arcus_agent_gateway-0.1.0/tests/fixtures/prices_AAPL.json +1 -0
- arcus_agent_gateway-0.1.0/tests/test_api.py +313 -0
- arcus_agent_gateway-0.1.0/tests/test_live_regressions.py +97 -0
- arcus_agent_gateway-0.1.0/tests/test_paths.py +38 -0
- arcus_agent_gateway-0.1.0/tests/test_server.py +502 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arcus-gateway
|
|
3
|
+
description: Market data for Robinhood Chain (Arcus) tokenized US equities via the arcus-agent-gateway MCP server. Use for quotes, corporate actions, splits, market status, and sector data on the 194 tokenized stocks. Includes the watchlist/price-tracking cron recipe.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Arcus Gateway (Robinhood Chain tokenized equities)
|
|
7
|
+
|
|
8
|
+
## What this is
|
|
9
|
+
|
|
10
|
+
`arcus-agent-gateway` is a read-only, keyless MCP server exposing market data
|
|
11
|
+
for the 194 tokenized US equities on Robinhood Chain (Arcus DEX). Prices come
|
|
12
|
+
raw from the REST API; multiplier-adjusted values are computed by the server
|
|
13
|
+
and always returned alongside the raw ones.
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- "What's X trading at?" / current bid-ask for a tokenized stock
|
|
18
|
+
- Halts, trading capabilities, market-wide status
|
|
19
|
+
- Upcoming splits / corporate actions (pending multiplier warnings)
|
|
20
|
+
- Sector comparisons (13-sector map) and symbol discovery
|
|
21
|
+
- Recurring price tracking of a symbol set → **watchlist recipe below**
|
|
22
|
+
|
|
23
|
+
Symbols are ticker-style (`AAPL`, `NVDA`). Discover them with `token_list()`
|
|
24
|
+
or `search()`; an unknown symbol raises an error that mentions `token_list()`.
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
| Tool | Call | Notes |
|
|
29
|
+
|------|------|-------|
|
|
30
|
+
| `token_list` | `token_list(status="ACTIVE", limit=100)` | Valid symbols; alphabetical; `limit` ≤ 500. |
|
|
31
|
+
| `quote` | `quote(symbol="AAPL")` | Raw + adjusted prices, `is_halted`, multiplier block. |
|
|
32
|
+
| `quotes` | `quotes(symbols=["AAPL","NVDA"])` | **Max 20 symbols per call.** Unknowns → `errors`, batch continues. |
|
|
33
|
+
| `token_detail` | `token_detail(symbol="AAPL")` | Dossier: contract, ISIN, actions, `warnings` (pending split). |
|
|
34
|
+
| `market_status` | `market_status()` | Cheap market health; `halted` lists only cached quotes. |
|
|
35
|
+
| `corporate_actions` | `corporate_actions(symbol=None, limit=10)` | Splits/dividends, all or one symbol. |
|
|
36
|
+
| `search` | `search(query="apple")` | `apple` → `AAPL`; top 10 with sector + score. |
|
|
37
|
+
| `sector_view` | `sector_view()` | 13 sectors; averages populate only after `quotes()` calls. |
|
|
38
|
+
| `onchain_info` | `onchain_info(symbol="AAPL")` | Contract/chain 4663/ISIN — v0.2 stub. |
|
|
39
|
+
|
|
40
|
+
Reading prices: report `bid_raw`/`ask_raw` as the REST price;
|
|
41
|
+
`bid_adjusted`/`ask_adjusted` = raw × `multiplier.current`. Always read
|
|
42
|
+
adjusted values next to the multiplier (a pending split changes the ratio).
|
|
43
|
+
|
|
44
|
+
## Watchlist / price tracking
|
|
45
|
+
|
|
46
|
+
There is deliberately **no watchlist tool**. Tracking = a scheduled
|
|
47
|
+
`quotes()` call. Use the agent's cron (e.g. Hermes cron), not a tight loop.
|
|
48
|
+
|
|
49
|
+
**Rules:**
|
|
50
|
+
|
|
51
|
+
1. Interval **≥ 5 minutes**. The server's price cache is 15 s and the client
|
|
52
|
+
caps itself at ≤ 50 req/s — polling faster than 5 min adds nothing and
|
|
53
|
+
wastes the upstream budget (60 req/s keyless limit is shared).
|
|
54
|
+
2. **≤ 20 symbols per `quotes()` call** — more raises a `ValueError`. Split
|
|
55
|
+
larger watchlists into multiple calls (or multiple cron entries).
|
|
56
|
+
3. One cron entry can cover the whole watchlist: `quotes()` accepts the list.
|
|
57
|
+
|
|
58
|
+
**Example** — track NVDA and TSLA every 5 minutes (Hermes cron):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"name": "watchlist-nvda-tsla",
|
|
63
|
+
"schedule": "*/5 * * * *",
|
|
64
|
+
"prompt": "Call quotes(symbols=[\"NVDA\",\"TSLA\"]) on the arcus MCP server. "
|
|
65
|
+
"Alert me if any quote has is_halted=true or a non-null "
|
|
66
|
+
"multiplier.pending; otherwise stay silent."
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Equivalent plain-cron line if your scheduler prefers shell:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
*/5 * * * * your-agent run --tool quotes --args '{"symbols":["NVDA","TSLA"]}'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Watch for in each poll:** `is_halted: true` (halt), a new
|
|
77
|
+
`multiplier.pending` (upcoming split — verify with
|
|
78
|
+
`token_detail(symbol=...)`), `errors[]` (a symbol was delisted/renamed —
|
|
79
|
+
re-check with `token_list()`).
|
|
80
|
+
|
|
81
|
+
## Caveats
|
|
82
|
+
|
|
83
|
+
- Read-only market data, **not trading advice**; verify against the official
|
|
84
|
+
source before acting.
|
|
85
|
+
- `market_status().halted` and `sector_view()` averages are cache-driven —
|
|
86
|
+
prime with `quotes()` for a real scan.
|
|
87
|
+
- `onchain_info` is a stub until v0.2 (no balances/history yet).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
contents: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v5
|
|
15
|
+
- uses: actions/setup-python@v6
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.13"
|
|
18
|
+
- run: pip install build twine
|
|
19
|
+
- run: python -m build
|
|
20
|
+
- run: python -m twine check dist/*
|
|
21
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
22
|
+
- name: GitHub Release
|
|
23
|
+
uses: softprops/action-gh-release@v2
|
|
24
|
+
with:
|
|
25
|
+
files: dist/*
|
|
26
|
+
generate_release_notes: true
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
offline:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python: ["3.11", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
- uses: actions/setup-python@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python }}
|
|
19
|
+
- run: pip install -e ".[dev]"
|
|
20
|
+
- name: Run tests
|
|
21
|
+
run: |
|
|
22
|
+
pytest tests/ -q --cov=arcus_mcp --tb=short > pytest_output.txt 2>&1 || FAILED=1
|
|
23
|
+
grep -E "^(FAILED|ERROR)|Error|error" pytest_output.txt | head -25 | while IFS= read -r line; do
|
|
24
|
+
echo "::error::${line:0:250}"
|
|
25
|
+
done
|
|
26
|
+
tail -40 pytest_output.txt
|
|
27
|
+
exit ${FAILED:-0}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-09-04)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- 9 read-only MCP tools for 194 tokenized US equities on Robinhood Chain
|
|
8
|
+
(Arcus DEX): `token_list`, `quote`, `quotes`, `token_detail`,
|
|
9
|
+
`market_status`, `corporate_actions`, `search`, `sector_view`,
|
|
10
|
+
`onchain_info` (v0.2 stub)
|
|
11
|
+
- Multiplier handling: raw and adjusted price fields side by side,
|
|
12
|
+
pending-split warnings (effective time), per-token multiplier history note
|
|
13
|
+
- Caching (300s / 15s / 3600s), client rate limit (50 req/s cap), 429 retry
|
|
14
|
+
with backoff
|
|
15
|
+
- Honest degradation: unknown symbol errors point at `token_list()`;
|
|
16
|
+
`market_status()` labels its estimates; `onchain_info` is a marked stub
|
|
17
|
+
- 75 offline tests including spec invariants (adjusted == raw x multiplier,
|
|
18
|
+
spread >= 0, halted propagation, batch limits)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 arcus-agent-gateway contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: arcus-agent-gateway
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP gateway for AI agents to Robinhood Chain (Arcus DEX) tokenized US equities: quotes, multipliers, corporate actions, market status, sectors. Read-only, keyless.
|
|
5
|
+
Project-URL: Homepage, https://github.com/alekskram/arcus-agent-gateway
|
|
6
|
+
Project-URL: Repository, https://github.com/alekskram/arcus-agent-gateway
|
|
7
|
+
Project-URL: Issues, https://github.com/alekskram/arcus-agent-gateway/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agents,arcus,defi,mcp,robinhood-chain,rwa,tokenized-stocks
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: fastmcp>=3.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pip-audit; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# arcus-agent-gateway
|
|
28
|
+
|
|
29
|
+
[](https://github.com/alekskram/arcus-agent-gateway/actions/workflows/tests.yml)
|
|
30
|
+
[](https://pypi.org/project/arcus-agent-gateway/)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](pyproject.toml)
|
|
33
|
+
|
|
34
|
+
An MCP (Model Context Protocol) server that gives AI agents read-only, keyless
|
|
35
|
+
access to market data for the **194 tokenized US equities** on **Robinhood Chain
|
|
36
|
+
(Arcus)** — quotes, corporate actions, trading capabilities, multipliers and a
|
|
37
|
+
13-sector map. No API keys, no auth, no writes: every tool is a GET against the
|
|
38
|
+
public `api.robinhood.com/rhj` REST surface, cached and rate-limited so an
|
|
39
|
+
enthusiastic agent can't hammer the upstream.
|
|
40
|
+
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
Run over stdio (the default, for local agents):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uvx arcus-agent-gateway
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Claude Desktop / Cursor config (`claude_desktop_config.json` or
|
|
50
|
+
`.cursor/mcp.json`):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"arcus": {
|
|
56
|
+
"command": "uvx",
|
|
57
|
+
"args": ["arcus-agent-gateway"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Hosted form — streamable HTTP on port **8902**:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uvx arcus-agent-gateway --http # 127.0.0.1:8902
|
|
67
|
+
curl http://127.0.0.1:8902/health # -> {"ok": true, "service": "arcus-agent-gateway"}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Tools
|
|
71
|
+
|
|
72
|
+
All 9 tools are read-only (annotated `readOnlyHint: true`). Names and
|
|
73
|
+
parameters are exactly as registered by `arcus_mcp/server.py`.
|
|
74
|
+
|
|
75
|
+
| # | Tool | Signature | What it does |
|
|
76
|
+
|---|------|-----------|--------------|
|
|
77
|
+
| 1 | `token_list` | `token_list(status="ACTIVE", limit=100)` | Tokenized equities, one row per token (symbol, name, status, multiplier, tradable); `status` filters the `ASSET_STATUS_*` prefix, `'ALL'` disables. Start here for valid symbols. |
|
|
78
|
+
| 2 | `quote` | `quote(symbol)` | Live quote joined with asset metadata: raw + multiplier-adjusted bid/ask/spread, `is_halted`, trading capabilities, multiplier block. Unknown symbol raises with a pointer to `token_list()`. |
|
|
79
|
+
| 3 | `quotes` | `quotes(symbols)` | Batch of `quote()` rows, **max 20 per call** (more raises). Unknown symbols land in `errors` without failing the batch. |
|
|
80
|
+
| 4 | `token_detail` | `token_detail(symbol)` | Full dossier: contract/chain/ISIN metadata, embedded quote, last 5 corporate actions, multiplier block with history note, `warnings` (pending split). |
|
|
81
|
+
| 5 | `market_status` | `market_status()` | Market-wide health from assets only (never fetches 194 prices): totals, untradable count, cached-halted list, extended-hours estimate. |
|
|
82
|
+
| 6 | `corporate_actions` | `corporate_actions(symbol=None, limit=10)` | Splits/dividends across all tokens or for one symbol; tolerant to the API's field-name variants. |
|
|
83
|
+
| 7 | `search` | `search(query)` | Local fuzzy search over the token list; `apple` → `AAPL`; top 10 with scores and sectors. |
|
|
84
|
+
| 8 | `sector_view` | `sector_view()` | 13-sector static map with sizes and — once quotes are cached — multiplier-adjusted sector averages. Makes no requests. |
|
|
85
|
+
| 9 | `onchain_info` | `onchain_info(symbol)` | Contract address, chain id (4663, Robinhood Chain), network, decimals, ISIN. v0.2 stub — full on-chain data (balances, Transfer/Split history) is planned for v0.2. |
|
|
86
|
+
| 10 | *watchlist* | — | Not a tool. Price tracking is done by your agent's scheduler (cron) calling `quotes()` on an interval — see [`.agents/skills/arcus-gateway/SKILL.md`](.agents/skills/arcus-gateway/SKILL.md). |
|
|
87
|
+
|
|
88
|
+
## Multiplier logic (read this before using prices)
|
|
89
|
+
|
|
90
|
+
Robinhood Chain tokens carry a **multiplier** — the corporate-action
|
|
91
|
+
adjustment factor for the token contract (`1.0` = untouched). Splits change it;
|
|
92
|
+
for example NVDA's 2026-11 split queues `pendingMultiplier: "4.0"`.
|
|
93
|
+
|
|
94
|
+
- **The REST API returns RAW prices.** `bid`/`ask` from `/prices/{symbol}` are
|
|
95
|
+
in token-contract units and are **not** multiplier-adjusted.
|
|
96
|
+
- **Adjusted values are computed by this server**, never taken from upstream:
|
|
97
|
+
`price_adjusted = round(price_raw × currentMultiplier, 6)`.
|
|
98
|
+
- **Raw and adjusted always travel together.** Every quote carries
|
|
99
|
+
`bid_raw`/`ask_raw`/`spread_raw` *and* `bid_adjusted`/`ask_adjusted`/
|
|
100
|
+
`mid_adjusted` next to the `multiplier` block — never one without the other.
|
|
101
|
+
- On-chain quantities (token balances, mint/burn volumes) are natively in
|
|
102
|
+
adjusted (multiplied) units; REST prices are not. If you compare the two,
|
|
103
|
+
go through the `*_adjusted` fields.
|
|
104
|
+
|
|
105
|
+
Worked example (live fixture, 2026-09-03):
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
AAPL currentMultiplier = 1.000566080061092436
|
|
109
|
+
bid_raw = 327.77 → bid_adjusted = round(327.77 × 1.000566…, 6) = 327.955544
|
|
110
|
+
ask_raw = 327.78 → ask_adjusted = 327.965550
|
|
111
|
+
mid mid_adjusted = 327.960547
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Pending split warning.** When `pendingMultiplier` is queued (non-empty) and
|
|
115
|
+
differs from the current one, `token_detail()` adds a warning like
|
|
116
|
+
`pending split: 1→4.0 on 2026-11-06T00:00:00Z`, and `quote()`'s multiplier
|
|
117
|
+
block exposes `pending` + `effective_time`. After the split lands, raw prices
|
|
118
|
+
jump by the ratio while `*_adjusted` fields stay comparable — another reason to
|
|
119
|
+
always read adjusted values next to the multiplier.
|
|
120
|
+
|
|
121
|
+
## API limits & caching
|
|
122
|
+
|
|
123
|
+
- Upstream allows **60 req/s without a key**; this client self-limits to
|
|
124
|
+
**≤ 50 req/s** (a 20 ms politeness interval between requests, thread-safe).
|
|
125
|
+
- Transient failures (`429/502/503/504`, network errors) are retried up to 3
|
|
126
|
+
times with `2s × (attempt+1)` backoff.
|
|
127
|
+
- Response caches (per process): `/assets` **5 min**, `/prices/{symbol}`
|
|
128
|
+
**15 s**, `/corporate-actions` **1 h**. `market_status()` and `sector_view()`
|
|
129
|
+
are computed from caches and assets only — they never fan out 194 price
|
|
130
|
+
requests.
|
|
131
|
+
|
|
132
|
+
## Raw prices disclaimer
|
|
133
|
+
|
|
134
|
+
Prices are served **exactly as they arrive from Robinhood (RAW)** — they are
|
|
135
|
+
*not* multiplier-adjusted, and the `*_adjusted` fields are **our computation**,
|
|
136
|
+
not upstream data. All data is for information only, **not for trading
|
|
137
|
+
decisions**, and should be verified against the official source before you act
|
|
138
|
+
on it. No warranty of completeness, accuracy or timeliness.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
|
|
144
|
+
.venv/bin/pytest -q # 75 tests, all offline (fixtures + mocks)
|
|
145
|
+
.venv/bin/python scripts/smoke_api_offline.py # REST client smoke, zero HTTP
|
|
146
|
+
.venv/bin/python scripts/smoke_server_offline.py # 9-tool server smoke, zero HTTP
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Layout: `arcus_mcp/api.py` (stdlib REST client), `arcus_mcp/server.py` (9 MCP
|
|
150
|
+
tools + FastMCP wiring), `arcus_mcp/sectors.py` (validated 13-sector map),
|
|
151
|
+
`arcus_mcp/paths.py` (state dir; override with `ARCUS_GATEWAY_DATA`).
|
|
152
|
+
Live-captured schema fixtures live in `tests/fixtures/` with notes in
|
|
153
|
+
`arcus_mcp/API_NOTES.md`. Usage scenarios: [`examples/use-cases.md`](examples/use-cases.md).
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# arcus-agent-gateway
|
|
2
|
+
|
|
3
|
+
[](https://github.com/alekskram/arcus-agent-gateway/actions/workflows/tests.yml)
|
|
4
|
+
[](https://pypi.org/project/arcus-agent-gateway/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](pyproject.toml)
|
|
7
|
+
|
|
8
|
+
An MCP (Model Context Protocol) server that gives AI agents read-only, keyless
|
|
9
|
+
access to market data for the **194 tokenized US equities** on **Robinhood Chain
|
|
10
|
+
(Arcus)** — quotes, corporate actions, trading capabilities, multipliers and a
|
|
11
|
+
13-sector map. No API keys, no auth, no writes: every tool is a GET against the
|
|
12
|
+
public `api.robinhood.com/rhj` REST surface, cached and rate-limited so an
|
|
13
|
+
enthusiastic agent can't hammer the upstream.
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
Run over stdio (the default, for local agents):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uvx arcus-agent-gateway
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Claude Desktop / Cursor config (`claude_desktop_config.json` or
|
|
24
|
+
`.cursor/mcp.json`):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"arcus": {
|
|
30
|
+
"command": "uvx",
|
|
31
|
+
"args": ["arcus-agent-gateway"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Hosted form — streamable HTTP on port **8902**:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uvx arcus-agent-gateway --http # 127.0.0.1:8902
|
|
41
|
+
curl http://127.0.0.1:8902/health # -> {"ok": true, "service": "arcus-agent-gateway"}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Tools
|
|
45
|
+
|
|
46
|
+
All 9 tools are read-only (annotated `readOnlyHint: true`). Names and
|
|
47
|
+
parameters are exactly as registered by `arcus_mcp/server.py`.
|
|
48
|
+
|
|
49
|
+
| # | Tool | Signature | What it does |
|
|
50
|
+
|---|------|-----------|--------------|
|
|
51
|
+
| 1 | `token_list` | `token_list(status="ACTIVE", limit=100)` | Tokenized equities, one row per token (symbol, name, status, multiplier, tradable); `status` filters the `ASSET_STATUS_*` prefix, `'ALL'` disables. Start here for valid symbols. |
|
|
52
|
+
| 2 | `quote` | `quote(symbol)` | Live quote joined with asset metadata: raw + multiplier-adjusted bid/ask/spread, `is_halted`, trading capabilities, multiplier block. Unknown symbol raises with a pointer to `token_list()`. |
|
|
53
|
+
| 3 | `quotes` | `quotes(symbols)` | Batch of `quote()` rows, **max 20 per call** (more raises). Unknown symbols land in `errors` without failing the batch. |
|
|
54
|
+
| 4 | `token_detail` | `token_detail(symbol)` | Full dossier: contract/chain/ISIN metadata, embedded quote, last 5 corporate actions, multiplier block with history note, `warnings` (pending split). |
|
|
55
|
+
| 5 | `market_status` | `market_status()` | Market-wide health from assets only (never fetches 194 prices): totals, untradable count, cached-halted list, extended-hours estimate. |
|
|
56
|
+
| 6 | `corporate_actions` | `corporate_actions(symbol=None, limit=10)` | Splits/dividends across all tokens or for one symbol; tolerant to the API's field-name variants. |
|
|
57
|
+
| 7 | `search` | `search(query)` | Local fuzzy search over the token list; `apple` → `AAPL`; top 10 with scores and sectors. |
|
|
58
|
+
| 8 | `sector_view` | `sector_view()` | 13-sector static map with sizes and — once quotes are cached — multiplier-adjusted sector averages. Makes no requests. |
|
|
59
|
+
| 9 | `onchain_info` | `onchain_info(symbol)` | Contract address, chain id (4663, Robinhood Chain), network, decimals, ISIN. v0.2 stub — full on-chain data (balances, Transfer/Split history) is planned for v0.2. |
|
|
60
|
+
| 10 | *watchlist* | — | Not a tool. Price tracking is done by your agent's scheduler (cron) calling `quotes()` on an interval — see [`.agents/skills/arcus-gateway/SKILL.md`](.agents/skills/arcus-gateway/SKILL.md). |
|
|
61
|
+
|
|
62
|
+
## Multiplier logic (read this before using prices)
|
|
63
|
+
|
|
64
|
+
Robinhood Chain tokens carry a **multiplier** — the corporate-action
|
|
65
|
+
adjustment factor for the token contract (`1.0` = untouched). Splits change it;
|
|
66
|
+
for example NVDA's 2026-11 split queues `pendingMultiplier: "4.0"`.
|
|
67
|
+
|
|
68
|
+
- **The REST API returns RAW prices.** `bid`/`ask` from `/prices/{symbol}` are
|
|
69
|
+
in token-contract units and are **not** multiplier-adjusted.
|
|
70
|
+
- **Adjusted values are computed by this server**, never taken from upstream:
|
|
71
|
+
`price_adjusted = round(price_raw × currentMultiplier, 6)`.
|
|
72
|
+
- **Raw and adjusted always travel together.** Every quote carries
|
|
73
|
+
`bid_raw`/`ask_raw`/`spread_raw` *and* `bid_adjusted`/`ask_adjusted`/
|
|
74
|
+
`mid_adjusted` next to the `multiplier` block — never one without the other.
|
|
75
|
+
- On-chain quantities (token balances, mint/burn volumes) are natively in
|
|
76
|
+
adjusted (multiplied) units; REST prices are not. If you compare the two,
|
|
77
|
+
go through the `*_adjusted` fields.
|
|
78
|
+
|
|
79
|
+
Worked example (live fixture, 2026-09-03):
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
AAPL currentMultiplier = 1.000566080061092436
|
|
83
|
+
bid_raw = 327.77 → bid_adjusted = round(327.77 × 1.000566…, 6) = 327.955544
|
|
84
|
+
ask_raw = 327.78 → ask_adjusted = 327.965550
|
|
85
|
+
mid mid_adjusted = 327.960547
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Pending split warning.** When `pendingMultiplier` is queued (non-empty) and
|
|
89
|
+
differs from the current one, `token_detail()` adds a warning like
|
|
90
|
+
`pending split: 1→4.0 on 2026-11-06T00:00:00Z`, and `quote()`'s multiplier
|
|
91
|
+
block exposes `pending` + `effective_time`. After the split lands, raw prices
|
|
92
|
+
jump by the ratio while `*_adjusted` fields stay comparable — another reason to
|
|
93
|
+
always read adjusted values next to the multiplier.
|
|
94
|
+
|
|
95
|
+
## API limits & caching
|
|
96
|
+
|
|
97
|
+
- Upstream allows **60 req/s without a key**; this client self-limits to
|
|
98
|
+
**≤ 50 req/s** (a 20 ms politeness interval between requests, thread-safe).
|
|
99
|
+
- Transient failures (`429/502/503/504`, network errors) are retried up to 3
|
|
100
|
+
times with `2s × (attempt+1)` backoff.
|
|
101
|
+
- Response caches (per process): `/assets` **5 min**, `/prices/{symbol}`
|
|
102
|
+
**15 s**, `/corporate-actions` **1 h**. `market_status()` and `sector_view()`
|
|
103
|
+
are computed from caches and assets only — they never fan out 194 price
|
|
104
|
+
requests.
|
|
105
|
+
|
|
106
|
+
## Raw prices disclaimer
|
|
107
|
+
|
|
108
|
+
Prices are served **exactly as they arrive from Robinhood (RAW)** — they are
|
|
109
|
+
*not* multiplier-adjusted, and the `*_adjusted` fields are **our computation**,
|
|
110
|
+
not upstream data. All data is for information only, **not for trading
|
|
111
|
+
decisions**, and should be verified against the official source before you act
|
|
112
|
+
on it. No warranty of completeness, accuracy or timeliness.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
|
|
118
|
+
.venv/bin/pytest -q # 75 tests, all offline (fixtures + mocks)
|
|
119
|
+
.venv/bin/python scripts/smoke_api_offline.py # REST client smoke, zero HTTP
|
|
120
|
+
.venv/bin/python scripts/smoke_server_offline.py # 9-tool server smoke, zero HTTP
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Layout: `arcus_mcp/api.py` (stdlib REST client), `arcus_mcp/server.py` (9 MCP
|
|
124
|
+
tools + FastMCP wiring), `arcus_mcp/sectors.py` (validated 13-sector map),
|
|
125
|
+
`arcus_mcp/paths.py` (state dir; override with `ARCUS_GATEWAY_DATA`).
|
|
126
|
+
Live-captured schema fixtures live in `tests/fixtures/` with notes in
|
|
127
|
+
`arcus_mcp/API_NOTES.md`. Usage scenarios: [`examples/use-cases.md`](examples/use-cases.md).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# rhj API schema notes (live capture 2026-09-03)
|
|
2
|
+
|
|
3
|
+
Non-obvious details for anyone building on `arcus_mcp/api.py`. Fixtures:
|
|
4
|
+
`tests/fixtures/assets.json`, `tests/fixtures/prices_AAPL.json`.
|
|
5
|
+
|
|
6
|
+
1. **Every numeric field is a STRING.** `bid: "327.77"`,
|
|
7
|
+
`dailyTradingVolume: "25806784"`, and
|
|
8
|
+
`currentMultiplier: "1.000000000000000000"` — fixed 18-decimal
|
|
9
|
+
strings (token-style scaling, though `tokenDecimals` is also 18).
|
|
10
|
+
`float(x)` before any arithmetic; never `int()` directly.
|
|
11
|
+
2. **`pendingMultiplier` can be `""`** — empty string, not `null`/`0`,
|
|
12
|
+
when no multiplier change is queued. Truthiness (`if a["pendingMultiplier"]`)
|
|
13
|
+
is the right liveness check; never parse it unconditionally.
|
|
14
|
+
3. **`/prices/{symbol}` wraps the quote in a list** — `{"quotes": [ {...} ]}`
|
|
15
|
+
with exactly one element per fixture. Take `quotes[0]`, but guard for an
|
|
16
|
+
empty list. There is **no name or multiplier in the quote**: a full
|
|
17
|
+
ticker view = join `assets()` ↔ `prices()` on `tokenSymbol`.
|
|
18
|
+
4. **`generatedAt` carries nanosecond precision**
|
|
19
|
+
(`2026-09-03T19:43:54.478722091Z`). Python 3.11+ `datetime.fromisoformat`
|
|
20
|
+
handles it after replacing the `Z` suffix with `+00:00` (3.11+ also
|
|
21
|
+
accepts the bare `Z`).
|
|
22
|
+
5. **`/corporate-actions` response shape was NOT captured live** — no
|
|
23
|
+
fixture exists. `corporate_actions()` therefore accepts both a bare
|
|
24
|
+
JSON list and a dict-wrapped list (first list value found). Confirm the
|
|
25
|
+
real wrapper key on the first live call and tighten if desired. Symbol
|
|
26
|
+
filtering is done locally (query params not contractual).
|
|
27
|
+
6. **Unknown symbol on `/prices/{x}` returns 404** — `prices()` maps that
|
|
28
|
+
to `None` (distinguishing "no quote" from "API down" is left to `get()`'s
|
|
29
|
+
exceptions). `asset()` likewise returns `None` for unknown symbols.
|