{
  "markdown": "# ibkr-portfolio-builder-mcp\n\nA remote MCP server for **top-down portfolio construction with Interactive Brokers**. Built to be used as a Claude.ai custom connector, Claude Code MCP, or any HTTP MCP client.\n\n> *Most ibkr-mcp servers expose individual lookup primitives — `get_quote`, `get_position`, `place_order`. This one exposes the **research workflow**: a typed catalog of 468 screeners across 16 categories tagged by strategy intent (value / growth / income / momentum / quality / events / …), per-scan applicable instruments, inverse-pair links, and live news/account access — so an LLM can do real top-down portfolio construction (pick sectors / strategies → run scans → cross-reference with news → narrow to candidates) instead of bottoms-up ticker fishing.*\n\n## Why this exists\n\nI built this because the existing IBKR-MCP servers in the community treat IBKR as a \"look-up-one-ticker\" data source. That mirrors how most retail brokerage UIs work, but it's not how good portfolio construction actually happens.\n\nA good top-down workflow looks like:\n\n1. **Macro thesis** (\"rates are about to fall, dividend payers should re-rate\") →\n2. **Strategy intent** (\"show me income screens with quality bias on US large caps\") →\n3. **Screener composition** (run dividend yield + ROE + low debt screens; intersect) →\n4. **Event/news context** (\"any of these have earnings in the next two weeks? any negative analyst actions?\") →\n5. **Narrow candidate set** (\"five tickers, ranked by my criteria, ready for deeper diligence\").\n\nTo do that with an LLM, the MCP server needs to expose **the research vocabulary**, not just the raw API. That's the gap this server fills:\n\n- **A typed scan catalog** — 468 IBKR scan codes across 16 categories (`Fundamentals`, `Price Movement`, `Dividends`, `Options & Volatility`, `Events & Earnings`, `52/26/13 Week High-Low`, `ESG`, `Bonds`, …) auto-tagged with **28 strategy intent tags** (`value`, `growth`, `quality`, `income`, `momentum_up`, `momentum_down`, `analyst`, `technical`, `gap`, `volatility`, `events`, `leverage`, `efficiency`, `risk_adjusted`, …). The LLM asks \"what value scans exist for US stocks?\" and gets a clean, filterable answer instead of trying to guess scan codes from training data.\n- **Inverse pair links** — every `HIGH_X ↔ LOW_X` and `X_ASC ↔ X_DESC` pair is precomputed, so the LLM can flip polarity (\"what's the opposite of LOW_PE_RATIO?\") without guessing.\n- **Per-scan instrument map** — the catalog knows which scan applies to `STK`, `ETF`, `OPT`, `BOND`, etc. The LLM stops sending Refinitiv scans to bond instruments and getting empty results.\n- **Filter catalog** — separate typed map of the numeric filters (`priceAbove`, `peRatioBelow`, `divYieldAbove`, `growthRateAbove`, `avgVolumeAbove`, `marketCapAbove`, …) grouped by category, with per-instrument applicability notes.\n- **News + screeners in one tool surface** — same connector, same auth, same conversation. The LLM can intersect a scan result with recent headlines or upcoming earnings without context-switching.\n- **Two auth modes** — full OAuth 2.1 (DCR + PKCE + well-knowns) for Claude.ai custom connectors, plus a static bearer token for everything else. Same server, same tools.\n\nIt's still an early server. The IBKR API has plenty of restrictions on what a paper account can actually see (notably historical news entitlement). But the *catalog* and *workflow shape* are production-ready, and they're the load-bearing piece for an LLM-driven research loop.\n\n## Quick facts\n\n- **Transport:** Streamable HTTP at `/mcp`.\n- **Auth:** OAuth 2.1 (PKCE + RFC 7591 Dynamic Client Registration) AND/OR static bearer token. Selectable via `AUTH_MODE`.\n- **Persistence:** in-memory only today (sessions / DCR clients / OAuth tokens reset on container restart). Redis is on the roadmap — see below.\n- **IBKR connection:** ib-gateway (`ghcr.io/gnzsnz/ib-gateway:stable`) runs as a sibling service in this compose; paper account in read-only API mode by default.\n- **Built on:** [FastMCP](https://gofastmcp.com/) + [ib_async](https://github.com/ib-api-reloaded/ib_async).\n\n## Tools\n\nParity with IBKR's official MCP for the **9 read-only tools** (skipping the two write/order-instruction tools — see roadmap), plus the 5 screener/news/catalog tools that are this server's reason for existing.\n\n| Tool | What it does |\n|---|---|\n| `ib_account_summary` | NetLiquidation / BuyingPower / TotalCashValue / AvailableFunds / UnrealizedPnL for the connected paper or live account. |\n| `ib_positions` | Open positions across managed accounts, with quantity / avg cost / mark-to-market / unrealized PnL. |\n| `ib_open_orders` | Currently working orders with status, filled / remaining quantity, average fill price. |\n| `ib_trades` | Recent executed fills (`days_back` window, IBKR caps history ~7 days). |\n| `ib_price_snapshot` | Current bid / ask / last / high / low / volume for a US stock. Surfaces IBKR market-data restriction messages clearly. |\n| `ib_price_history` | OHLCV bars for any duration / bar size (`1 day`, `1 hour`, `5 mins`, ...). Always works regardless of market-data subscription. |\n| `ib_search_contracts` | Fuzzy-search IBKR's contract database by name / partial ticker. |\n| `ib_contract_details` | Full contract metadata — `long_name`, `industry`, `category`, `subcategory`, trading hours, valid exchanges. **The hook for sector-aware top-down screening.** |\n| `ib_scan_catalog` | The typed scan catalog. Filter by `category`, `strategy`, `instrument`, free-text `query`; optionally return the full list of available categories + strategies via `list_meta=true`. |\n| `ib_filter_catalog` | Filter parameter codes grouped by category (`price`, `volume`, `market_cap`, `fundamentals`, `technical`, `options`), with an instrument applicability note. |\n| `ib_screener_codes` | Substring search over the raw `scan-parameters.xml` codes (legacy / fallback). Useful for newer vendor codes not yet in the curated catalog. |\n| `ib_screener` | Run an IBKR scan — pass `scan_code`, `instrument`, `location`, optional price / volume filters. Returns rank + ticker + exchange. |\n| `ib_news_providers` | List subscribed IBKR news providers (Briefing.com, Dow Jones, etc.). |\n| `ib_news_for_symbol` | Fetch headlines for a US stock across all subscribed providers. Headlines only; surfaces a clear `notice` field when the account lacks historical-news entitlement. |\n| `ib_news_article` | Fetch a single article body by `provider_code` + `article_id`. ⚠️ may incur a per-article fee (Dow Jones in particular). |\n\n> **Note on order placement.** IBKR's official MCP also exposes `Create Order Instruction` and `Delete Order Instruction`. This server intentionally **does not** — it runs ib-gateway in `READ_ONLY_API=yes` mode so even a misrouted tool call cannot place an order. Order execution belongs in a separate service with its own approval gate. See the roadmap for a possible \"staged-only\" instruction tool that would write to a local store without ever touching IBKR.\n\n## Authentication\n\n`AUTH_MODE` selects which mechanisms the server accepts. The default is `both`.\n\n| Mode | What's accepted | Required env |\n|---|---|---|\n| `oauth` | OAuth-issued tokens only. Required for Claude.ai custom connectors. | `LOGIN_PASSWORD` |\n| `bearer` | A static `Authorization: Bearer <token>` only. Skips the OAuth dance — best for CLI clients, Claude Code, your own scripts. | `STATIC_BEARER_TOKEN` |\n| `both` *(default)* | Either OAuth tokens or the static bearer token. | At least one of `LOGIN_PASSWORD` or `STATIC_BEARER_TOKEN`. |\n\nThe OAuth endpoints (`/authorize`, `/token`, `/register`, `/.well-known/*`, `/login`) are always registered. In `bearer` mode they're inert — nothing in your README needs to point at them.\n\n### Generating secrets\n\n```bash\nuv run --no-project python -c \"import secrets; print(secrets.token_urlsafe(48))\"   # bearer token\nuv run --no-project python -c \"import secrets; print(secrets.token_urlsafe(48))\"   # session secret\n```\n\n### Using the static bearer token\n\n```bash\ncurl -X POST https://YOUR.DOMAIN/mcp \\\n  -H \"Authorization: Bearer $STATIC_BEARER_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}'\n```\n\n### Connecting from Claude.ai (OAuth)\n\nSettings → Connectors → **Add custom connector** → URL: `https://YOUR.DOMAIN/mcp` → leave OAuth client id/secret blank (Claude.ai uses DCR). When Claude.ai opens the OAuth flow, you'll be prompted for `LOGIN_PASSWORD`.\n\n## Run\n\n```bash\ncp .env.example .env\n# edit .env: set TWS_USERID / TWS_PASSWORD (paper account), LOGIN_PASSWORD, STATIC_BEARER_TOKEN, PUBLIC_BASE_URL\ndocker compose up -d\n```\n\nBy default this pulls the published multi-arch image from GitHub Container Registry: `ghcr.io/adwiteeymauriya/ibkr-portfolio-builder-mcp:latest`. To build locally instead (e.g. when iterating on the source), run `docker compose build ibkr-mcp` first.\n\nib-gateway takes ~60–90 s to finish IBKR login after first start. Tail logs with `docker logs -f ibkr-mcp-gateway`.\n\nFor local-only use (bearer-token clients, no Claude.ai) this is enough — `http://localhost:8000/mcp` is now serving. Claude.ai custom connectors require an **HTTPS URL on the public internet**, so a reverse proxy with TLS in front is needed.\n\n### Putting TLS in front for Claude.ai\n\nPick one. Both end with a working `https://your-host/mcp` and a valid cert.\n\n**Option 1 — Cloudflare Tunnel (no public IP, no port forwarding).** Best if the server runs on a home network or a VM behind NAT. Cloudflare gives you a hostname and TLS for free; the tunnel daemon dials out from the server to Cloudflare's edge.\n\n```bash\n# One-time: install cloudflared, then\ncloudflared tunnel login\ncloudflared tunnel create ibkr-mcp\ncloudflared tunnel route dns ibkr-mcp ibkr-mcp.your-domain.com\n```\n\n`~/.cloudflared/config.yml`:\n\n```yaml\ntunnel: ibkr-mcp\ncredentials-file: /home/you/.cloudflared/<TUNNEL_ID>.json\ningress:\n  - hostname: ibkr-mcp.your-domain.com\n    service: http://localhost:8000\n  - service: http_status:404\n```\n\nRun with `cloudflared tunnel run ibkr-mcp` (or install as a systemd unit via `cloudflared service install`). Then set `PUBLIC_BASE_URL=https://ibkr-mcp.your-domain.com` in `.env` and `docker compose restart ibkr-mcp`.\n\n**Option 2 — Caddy reverse proxy with Let's Encrypt.** Best if the server has a public IP and ports 80/443 open. Caddy fetches certs automatically.\n\n`Caddyfile`:\n\n```\nibkr-mcp.your-domain.com {\n    reverse_proxy localhost:8000\n}\n```\n\nRun with `caddy run` (or install as a system service: `sudo caddy start` + a systemd unit). Same `.env` change as above.\n\nIn both cases `PUBLIC_BASE_URL` must match exactly the URL you give Claude.ai — Claude.ai validates the OAuth issuer against it.\n\n## Example LLM prompts (top-down workflow)\n\n```\n1. Discovery:\n   \"List the strategies and categories available in ib_scan_catalog.\"\n\n2. Strategy intent:\n   \"Find me value scans for US stocks. Show me their inverse codes too.\"\n\n3. Composition:\n   \"Run LOW_PE_RATIO on STK.US.MAJOR with priceAbove $20 and avgVolumeAbove\n    1,000,000, top 30. Cross-reference with HIGH_RETURN_ON_EQUITY top 30.\n    Show me overlap.\"\n\n4. Event context:\n   \"For the overlap list, check ib_news_for_symbol for any negative\n    headlines in the last 7 days, and Events & Earnings scans for\n    upcoming earnings within 14 days.\"\n\n5. Narrow:\n   \"Rank the survivors by liquidity and tell me which two you'd dig\n    into next.\"\n```\n\n## Roadmap\n\n| Area | Item |\n|---|---|\n| Auth | Redis-backed sessions, DCR clients, OAuth tokens |\n| Auth | Per-token scopes (`read-only`, `read-news`, `screener-only`, ...) |\n| Instruments | Options (chains, greeks, IV/price calc) |\n| Instruments | Futures (`ContFuture`, combos via `Bag`) |\n| Instruments | Bonds (search + quote) |\n| Instruments | Forex + crypto |\n| Exchanges | Non-US equity routings (EU, HK, JP, AU) |\n| Exchanges | Currency-aware `ib_account_summary` |\n| Research | Reuters fundamentals (`reqFundamentalDataAsync`) |\n| Research | Real-time streaming bars + tick-by-tick |\n| Research | Level 2 order book |\n| Research | Daily PnL streams (`pnlAsync`, `pnlSingleAsync`) |\n| Research | Advisor sub-accounts (`reqFamilyCodesAsync`) |\n| Research | Catalog → Memgraph for multi-hop queries |\n| Research | Broader `ib_news_search` |\n| Risk | `ib_what_if_order` (margin preview, no execution) |\n| Risk | Local staged-instruction tools (no IBKR write) |\n\nOpen issues / PRs welcome on any of these.\n\n## Layout\n\n```\n.\n├── Dockerfile\n├── docker-compose.yml             # connector + ib-gateway, internal IBKR network\n├── pyproject.toml                 # uv-managed: fastmcp, itsdangerous, uvicorn, ib_async\n├── uv.lock\n├── .env.example\n├── LICENSE                        # MIT\n├── scan-parameters.xml            # IBKR's authoritative scan params (raw XML)\n├── scanner_reference.json         # IBKR-categorized scanner reference\n├── scanner_params.json            # Flat dump of scan codes + filters\n├── scripts/\n│   └── build_catalog.py           # Regenerates src/connector/data/* from the three source files above\n└── src/\n    └── connector/\n        ├── settings.py            # env-driven config (incl. AUTH_MODE)\n        ├── auth.py                # LoginGatedOAuthProvider + static bearer override + /login\n        ├── ibkr.py                # ib_async connection helper (connect-per-call)\n        ├── screener.py            # raw scan-parameters.xml substring search (fallback tool)\n        ├── catalog.py             # typed scan + filter catalog loaders + filtering\n        ├── tools.py               # 15 MCP tools wired into FastMCP\n        ├── server.py              # FastMCP + Starlette wiring + uvicorn entry\n        └── data/\n            ├── scan_catalog.json  # generated\n            └── filter_catalog.json # generated\n```\n\n## License\n\n[MIT](./LICENSE).\n\n## Acknowledgements\n\n- [gnzsnz/ib-gateway-docker](https://github.com/gnzsnz/ib-gateway-docker) for the headless ib-gateway image.\n- [ib_async](https://github.com/ib-api-reloaded/ib_async) for the async IBKR client.\n- [FastMCP](https://gofastmcp.com/) for the MCP server framework with built-in OAuth support.\n\n## Disclaimer\n\nThis software talks to your Interactive Brokers account. By default it runs against a **paper account in read-only API mode** — orders cannot be placed even if a tool tries. If you switch to a live account, you do so at your own risk. None of the tool output constitutes investment advice; the strategy tagging is a vocabulary helper, not a recommendation engine.\n",
  "bytes": 14557,
  "sha": "c9be5dc6a66e09cccefe104e171730da5291da0139d2fea2d9f651ad508c75ee",
  "repo_slug": "adwiteeymauriya/ibkr-portfolio-builder-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_adwiteeymauriya_ibkr_portfolio_c17b7d75/readme"
}