docs: Remove sibling product and lab path leaks
Outsiders reading this module should not see private sibling names (tradey), unfinished rewire notes, or /fast/projects lab paths. Keep the library self-contained in README, design, plan, and test fixtures/identity strings.
This commit is contained in:
@@ -2,24 +2,24 @@
|
||||
|
||||
**Date:** 2026-09-01
|
||||
**Status:** approved design, pending implementation plan
|
||||
**Product:** `robinhood-agentic-mcp` — a Go library that presents the full Robinhood Agentic MCP with the same transport and auth tradey already uses.
|
||||
**Product:** `robinhood-agentic-mcp` — a Go library that presents the full Robinhood Agentic MCP over OAuth, a token file, streamable HTTP, and JSON-RPC fallback.
|
||||
|
||||
Not investment advice. The caller is responsible for every fill in the Robinhood Agentic account.
|
||||
|
||||
## Goal
|
||||
|
||||
A standalone Go module at `/fast/projects/golang/robinhood-agentic-mcp` that:
|
||||
A standalone Go module that:
|
||||
|
||||
1. Connects to `https://agent.robinhood.com/mcp/trading` the way tradey does today (OAuth, token file, streamable HTTP session, JSON-RPC fallback).
|
||||
1. Connects to `https://agent.robinhood.com/mcp/trading` (OAuth, token file, streamable HTTP session, JSON-RPC fallback).
|
||||
2. Exposes a typed Go method for every tool on that MCP (equity, options, crypto, watchlists, market data, scanner, accounts).
|
||||
3. Uses Alpaca-shaped money and order enums on the public API, translating to Robinhood’s string wire format internally.
|
||||
4. Can be imported later by tradey; this effort does **not** rewire tradey.
|
||||
4. Is importable by any Go app; this effort ships the library alone, not consumer rewires.
|
||||
|
||||
## Non-goals (v1)
|
||||
|
||||
- A CLI binary (`login` stays a library function; tradey keeps `tradey login`).
|
||||
- Rewiring tradey to import this module.
|
||||
- Tradey’s `Reader` / `Executor` / `Snapshot` / `Fake` desk types (paper/live policy stays in the app).
|
||||
- A CLI binary (`login` stays a library function).
|
||||
- Rewiring downstream apps to import this module.
|
||||
- App-level `Reader` / `Executor` / `Snapshot` / `Fake` desk types (paper/live policy stays in the app).
|
||||
- Codegen from `tools/list`.
|
||||
- Proxying or re-hosting the MCP as a server.
|
||||
- Banking / credit-card MCP (`agent.robinhood.com` non-trading endpoints).
|
||||
@@ -47,7 +47,7 @@ func Connect(ctx context.Context, cfg Config) (*API, error)
|
||||
| `rh.Connect` | Read `cfg.TokenFile`, open a session identified as `cfg.Name`/`cfg.Version`, return an `rh.API` with all subclients wired. Fail closed if tokens are missing or the session cannot be used. |
|
||||
| `client.Call(ctx, name, args)` | Escape hatch: invoke any tool by MCP name and return JSON. |
|
||||
|
||||
Identity must be set on `Config` **before** `Login` or `Connect`. After a session exists it is not changeable. Apps that need a distinct Robinhood OAuth client (tradey, another bot) pass their own `Name`. Empty `Name`/`Version` keep the library defaults.
|
||||
Identity must be set on `Config` **before** `Login` or `Connect`. After a session exists it is not changeable. Apps that need a distinct Robinhood OAuth client pass their own `Name`. Empty `Name`/`Version` keep the library defaults.
|
||||
|
||||
Daemon/headless callers must not call `Login` (no browser). They call `Connect` with an existing token file.
|
||||
|
||||
@@ -56,7 +56,7 @@ Daemon/headless callers must not call `Login` (no browser). They call `Connect`
|
||||
Module path: `s1d3sw1ped/robinhood-agentic-mcp`
|
||||
Go version: `1.25`
|
||||
Default MCP URL: `https://agent.robinhood.com/mcp/trading`
|
||||
MCP SDK: `github.com/modelcontextprotocol/go-sdk` (same major as tradey, currently v1.7.0)
|
||||
MCP SDK: `github.com/modelcontextprotocol/go-sdk` (currently v1.7.0)
|
||||
|
||||
One shared session. Asset-class packages wrap it. The root package is a facade so a caller can import once.
|
||||
|
||||
@@ -97,18 +97,18 @@ func Login(ctx context.Context, cfg Config) (accountID string, err error) // re-
|
||||
|
||||
Empty `cfg.URL` means `DefaultURL`. Subpackages also export `New(c client.Caller) *Client` so callers can wire a single package without the facade.
|
||||
|
||||
Each subclient depends only on `client.Caller`. Unit tests inject a stub; transport tests hit `rhntest`. A future tradey adapter can inject the same caller.
|
||||
Each subclient depends only on `client.Caller`. Unit tests inject a stub; transport tests hit `rhntest`. Downstream adapters can inject the same caller.
|
||||
|
||||
## Auth and transport
|
||||
|
||||
Copied from tradey’s working path (`internal/broker/oauth.go`, `login.go`, `mcp.go`). Identity is caller-owned via `Config.Name` / `Config.Version` so each app is a distinct Robinhood OAuth client (tradey, this library’s default, Grok-chat MCP, and so on).
|
||||
Auth and session code lives in `auth/` and `client/`. Identity is caller-owned via `Config.Name` / `Config.Version` so each app is a distinct Robinhood OAuth client (this library’s default, or any caller-chosen name).
|
||||
|
||||
- MCP `Implementation.Name` / `Implementation.Version`: `cfg.Name`, `cfg.Version` (defaults `robinhood-agentic-mcp` / `0.1.0`).
|
||||
- OAuth dynamic client registration `ClientName`: the same `cfg.Name`.
|
||||
- OAuth callback page text uses `cfg.Name` (not a hardcoded “Tradey is signed in”).
|
||||
- OAuth callback page text uses `cfg.Name` (not a hardcoded product string).
|
||||
- `StreamableClientTransport` with `DisableStandaloneSSE: true`.
|
||||
- Prefer the SDK session `CallTool`. If session connect fails, fall back to HTTP POST JSON-RPC `tools/call` with `Authorization: Bearer` and `Accept: application/json, text/event-stream`.
|
||||
- Token file JSON matches tradey’s `TokenSet` (access, refresh, type, expiry, client_id/secret, auth/token/redirect URLs, account_id). Mode `0600`.
|
||||
- Token file JSON is `TokenSet` (access, refresh, type, expiry, client_id/secret, auth/token/redirect URLs, account_id). Mode `0600`.
|
||||
- `Login` timeout: 5 minutes for the OAuth dance.
|
||||
- Session connect does not prompt; expired OAuth returns an error telling the caller to run `Login` again.
|
||||
- Connect does **not** write refreshed tokens back to disk (v1). The token file is whatever `Login` last wrote.
|
||||
@@ -138,7 +138,7 @@ Public money, size, and price fields use `github.com/alpacahq/alpacadecimal` imp
|
||||
- Optional amounts: `*decimal.Decimal` (Alpaca `PlaceOrderRequest.Qty` / `LimitPrice`).
|
||||
- Never `float64` for money, size, prices, buying power, volume, or bar OHLC.
|
||||
|
||||
On the way out, encode with `Decimal.String()` onto Robinhood’s string fields (`quantity`, `limit_price`, `stop_price`, `dollar_amount`, option `price`). On the way in, accept JSON string **or** number (tradey’s envelope) and parse into `decimal.Decimal`.
|
||||
On the way out, encode with `Decimal.String()` onto Robinhood’s string fields (`quantity`, `limit_price`, `stop_price`, `dollar_amount`, option `price`). On the way in, accept JSON string **or** number (Robinhood envelopes) and parse into `decimal.Decimal`.
|
||||
|
||||
Parse rules for money/size/price:
|
||||
|
||||
@@ -226,7 +226,7 @@ Source of truth for coverage is a frozen `testdata/tools.json` captured from liv
|
||||
| `get_crypto_account_onboarding_info` | `CryptoOnboardingInfo` |
|
||||
| `search` | `Search` |
|
||||
|
||||
This library does **not** enforce tradey’s `supported()` account policy (IRA/UTMA/full margin bans). It returns whatever `get_accounts` returns, including `agentic_allowed`. Apps decide.
|
||||
This library does **not** enforce an app-level account policy (IRA/UTMA/full margin bans). It returns whatever `get_accounts` returns, including `agentic_allowed`. Apps decide.
|
||||
|
||||
### `equity`
|
||||
|
||||
@@ -246,7 +246,7 @@ This library does **not** enforce tradey’s `supported()` account policy (IRA/U
|
||||
| `place_equity_order` | `PlaceOrder` |
|
||||
| `cancel_equity_order` | `CancelOrder` |
|
||||
|
||||
`Historicals` accepts caller interval/bounds/adjustment (no hidden tradey `minute`/`regular` defaults). Batching more than 10 symbols is the caller’s job; the method sends one MCP call.
|
||||
`Historicals` accepts caller interval/bounds/adjustment (no hidden `minute`/`regular` defaults). Batching more than 10 symbols is the caller’s job; the method sends one MCP call.
|
||||
|
||||
### `options`
|
||||
|
||||
@@ -296,7 +296,7 @@ Crypto account numbers use `rhs_account_number` on the wire, named `RHSAccountNu
|
||||
| `add_option_to_watchlist` | `AddOption` |
|
||||
| `remove_option_from_watchlist` | `RemoveOption` |
|
||||
|
||||
No title-lookup helper in v1. Tradey’s `Watchlist(title)` (match title, then `get_watchlist_items`, keep equity/ETF) stays in tradey until rewire.
|
||||
No title-lookup helper in v1. Callers that want match-by-title then `get_watchlist_items` (optionally filtering equity/ETF) implement that in the app.
|
||||
|
||||
### `market`
|
||||
|
||||
@@ -346,9 +346,9 @@ Pagination: request structs take `Cursor string`; results expose `NextCursor` /
|
||||
|
||||
CI must not call the live broker or open a browser. Tests otherwise go as far as they can: they hit an in-process Robinhood MCP mock over HTTP, not only an injected `Caller`.
|
||||
|
||||
`internal/rhntest` is an `httptest.Server` that speaks Robinhood’s MCP surface the way tradey talks to it:
|
||||
`internal/rhntest` is an `httptest.Server` that speaks Robinhood’s MCP surface:
|
||||
|
||||
- Required protocol: JSON-RPC `tools/call` (tradey’s RPC fallback). Optional extra: streamable-HTTP `CallTool` if the SDK client can talk to the same httptest without extra machinery.
|
||||
- Required protocol: JSON-RPC `tools/call` (RPC fallback). Optional extra: streamable-HTTP `CallTool` if the SDK client can talk to the same httptest without extra machinery.
|
||||
- Requires `Authorization: Bearer` when the test sets a token.
|
||||
- Dispatches on tool name and returns checked-in envelope fixtures (`testdata/*.json`) shaped like live Robinhood (`data` wrappers, string-or-number amounts, quote/close objects).
|
||||
- Can return HTTP 4xx, JSON-RPC errors, and malformed bodies so `ToolError` and parse-error paths are exercised.
|
||||
@@ -358,7 +358,7 @@ Required tests:
|
||||
- **Transport:** `Connect` against `rhntest` (session if the mock can serve it, otherwise RPC fallback), bearer header, HTTP error → `ToolError`, identity `Name`/`Version` sent on the MCP initialize/OAuth client metadata.
|
||||
- **Every typed method:** (1) stub `Caller` asserting MCP tool name + JSON args including `decimal.String()`; (2) round-trip through `rhntest` with a realistic success fixture.
|
||||
- **Wire/decimal:** omitted/null/`""`/`0` vs unparseable (`"n/a"`, object) — the latter errors, the former does not.
|
||||
- Parser fixtures from tradey’s known envelopes: `accounts` with `data` wrapper, quotes as `{quotes:[{quote:{…}, close:{…}}]}`, historicals `{historicals:[{symbol, data_points}]}`, watchlists `title` vs `name`.
|
||||
- Parser fixtures from known Robinhood envelopes: `accounts` with `data` wrapper, quotes as `{quotes:[{quote:{…}, close:{…}}]}`, historicals `{historicals:[{symbol, data_points}]}`, watchlists `title` vs `name`.
|
||||
- `WriteTokens` mode `0600`.
|
||||
- `Login` from `ROBINHOOD_ACCESS_TOKEN` (no OAuth, no browser).
|
||||
- `ToolError` is `errors.As`-able.
|
||||
@@ -383,13 +383,13 @@ No live-MCP or browser-OAuth tests, including `//go:build live` smokes. Not defe
|
||||
- Typed methods work with an injected `Caller` **and** round-trip through `rhntest`.
|
||||
- `Connect`/`Login` accept `Config.Name` / `Config.Version`; empty uses library defaults.
|
||||
- `Login` with `ROBINHOOD_ACCESS_TOKEN` writes `0600` tokens.
|
||||
- `equity.PlaceOrder` sends `quantity`/`limit_price` as decimal strings, `time_in_force` `gfd`, `type` `limit` — matching tradey’s `placeArgs` for the same inputs.
|
||||
- `equity.PlaceOrder` sends `quantity`/`limit_price` as decimal strings, `time_in_force` `gfd`, `type` `limit`.
|
||||
- Every name in the frozen `tools/list` fixture has a method.
|
||||
- tradey still builds; this repo does not import tradey and tradey is not changed.
|
||||
- This module stands alone; it does not import or modify downstream apps.
|
||||
|
||||
## Out of scope until tradey rewire
|
||||
## Out of scope (v1)
|
||||
|
||||
- `Watchlist(title string) ([]string, error)` helper.
|
||||
- Composite `Snapshot`.
|
||||
- Paper `Executor` constructor split.
|
||||
- `replace` directive / `go.mod` change in tradey.
|
||||
- Downstream `go.mod` / `replace` rewires.
|
||||
|
||||
Reference in New Issue
Block a user