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.
@@ -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,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.log
5
+ .coverage
6
+ .pytest_cache/
@@ -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
+ [![CI](https://github.com/alekskram/arcus-agent-gateway/actions/workflows/tests.yml/badge.svg)](https://github.com/alekskram/arcus-agent-gateway/actions/workflows/tests.yml)
30
+ [![PyPI](https://img.shields.io/pypi/v/arcus-agent-gateway.svg)](https://pypi.org/project/arcus-agent-gateway/)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
32
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](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
+ [![CI](https://github.com/alekskram/arcus-agent-gateway/actions/workflows/tests.yml/badge.svg)](https://github.com/alekskram/arcus-agent-gateway/actions/workflows/tests.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/arcus-agent-gateway.svg)](https://pypi.org/project/arcus-agent-gateway/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](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.
@@ -0,0 +1,2 @@
1
+ """Arcus Agent Gateway - MCP server package."""
2
+ __version__ = "0.1.0"