Income Screener MCP Server
by amahpour
README.md
# Income Screener MCP Server
A Python [MCP](https://modelcontextprotocol.io) server that connects Claude to E\*Trade via [`pyetrade`](https://github.com/jessecooper/pyetrade) for income-oriented trading research:
- Real-time quotes (equities, preferreds, baby bonds)
- Option chain lookups
- Put-sell candidate screening with annualized yield filters
- Portfolio position monitoring
## Architecture
```
Claude (Claude Code / Claude Desktop)
↓ MCP protocol (stdio)
server.py (FastMCP + pyetrade)
↓ OAuth1 REST
E*Trade API (api.etrade.com)
```
## Prerequisites
1. An active E\*Trade brokerage account.
2. Developer API keys from [developer.etrade.com](https://developer.etrade.com). Production keys take 3–5 business days to approve.
3. Python 3.10+.
## Setup
```bash
pip install -r requirements.txt
cp .env.example .env
# edit .env and set ETRADE_CONSUMER_KEY / ETRADE_CONSUMER_SECRET
python auth.py
```
`auth.py` walks the OAuth1 flow: it prints a URL (and tries to open your browser), you authorize the app on E\*Trade's site, paste the verification code back into the terminal, and the resulting access tokens are written into `.env`.
> **Note:** E\*Trade access tokens expire at midnight ET. Re-run `python auth.py` each morning before using the server.
## Running the server
```bash
python server.py
```
The server speaks MCP over stdio and is designed to be launched by an MCP client (Claude Desktop, Claude Code, etc.) rather than run interactively.
## Claude Desktop config
Add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"income-screener": {
"command": "python",
"args": ["server.py"],
"cwd": "/home/amahpour/code/pyetrade-mcp"
}
}
}
```
## Tools
| Tool | Description |
|---|---|
| `get_quote(symbols)` | Real-time quotes for up to 25 symbols. |
| `get_option_expire_dates(symbol)` | Available option expirations for a symbol. |
| `get_option_chain(symbol, chain_type, expiry_date, strike_price_near, num_strikes)` | Pull an option chain window. |
| `screen_put_candidates(symbols, max_strike_pct, min_annual_yield, min_open_interest, months_out)` | Rank put-sell income candidates by annualized yield. |
| `get_portfolio()` | Positions for the first account on the authenticated profile. |
### Annualized yield
`screen_put_candidates` computes `(bid / strike) * (365 / DTE)` and filters by `min_annual_yield`. Results sort highest-yield first.
### Default watchlists
If you call `screen_put_candidates` with no `symbols`, it uses a built-in equity list (HPQ, HPE, PFE, T, VZ, F, INTC, DOW, KHC, WBA). A preferred/baby-bond watchlist is defined in `server.py` for use with `get_quote`.
## Notes
- `server.py` uses `dev=False` (production API). E\*Trade's sandbox does not return real option chain data.
- Preferred stock symbol formatting (e.g. `JPM-C` vs `JPMpC`) may vary; test against your specific tickers.
- Calls in `screen_put_candidates` are serialized with a 0.5s delay to stay under E\*Trade's rate limits.
## Files
```
.
├── README.md
├── LICENSE
├── requirements.txt # pyetrade, mcp[cli], python-dotenv
├── .env.example # template — copy to .env and fill in
├── auth.py # one-shot OAuth token refresh
└── server.py # FastMCP server + all 5 tools
```