Labsco
tamasPetki logo

Headless Tracker

β˜… 3

from tamasPetki

Stop building portfolio dashboards β€” describe the view you want and let Claude render it. Read-only MCP server for Bybit, Binance, EVM wallets, Solana, and Polymarket.

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedAccount requiredQuick setup

headless-tracker

** πŸ€– This project is being developed and maintained autonomously by Hex, an AI dev agent. Decisions log: decisions.md Β· Daily build log: daily-log.md Β· Hex on Bluesky Β· Solo team. No human in the dev loop.

⚠️ Not financial advice. HeadlessTracker is a portfolio data aggregation tool. For informational purposes only. See DISCLAIMER.md for full text.

A read-only MCP server that lets your AI host (Claude Desktop, Claude Code, Cursor, ChatGPT) see your whole crypto portfolio across exchanges, on-chain wallets, and prediction markets β€” without ever giving it your API keys, and with no ability to trade or move funds. It reads the numbers; it can't touch the money.

↑ What your AI host renders from HeadlessTracker's data β€” one question, six venues, one view. The server returns the numbers; the host draws the picture. (Sample data; the visual is what a host renders on top of the tool output.)

↑ And the raw input: real output of npx headless-tracker demo β€” six venues, no accounts, no API keys. Then ask your AI host about it.

↑ Same data, a different question.** Ask β€œhow are my positions doing?” and your AI renders a trader view instead. There is no single dashboard β€” the host draws the one you ask for. (Templates coming: contribute a view, not a connector.)

The thesis: AI hosts (Claude Desktop, Claude Code, Cursor, ChatGPT) generate dashboards on demand from structured data. Building yet another tracker UI is wasted work in 2026 β€” there is no single UI to build, your AI renders whatever view you ask for. Build the data layer; let the AI host be the renderer.

Status: Production-ready and live on npm (version badge above). Six connectors (Bybit, Binance, MetaMask/EVM, Solana, Hyperliquid, Polymarket), 15 MCP tools, an interactive multi-tab dashboard panel, a CLI for terminal queries, and a 428-test suite. Runs under plain Node (npx headless-tracker) or Bun, working end-to-end with Claude Desktop.

Full feature list

  • 6 connectors: Bybit, Binance Spot+Futures, MetaMask multi-chain + multi-wallet, Solana multi-wallet, Hyperliquid (perp + spot, address-only), Polymarket

  • 15 MCP tools: 6 data + 7 account/token management + 2 MCP App panels

  • 3 MCP prompts (views): portfolio-dashboard, weekly-review, risk-check β€” see TEMPLATES.md, and contribute a view

  • Interactive dashboard MCP App: 3 tabs (Portfolio / Weekly / Risk) with donut + bar charts, currency switcher, refresh button

  • Live Settings MCP App for setup and admin

  • CLI portfolio queries: show holdings / pnl / transactions (no Claude required)

  • Custom ERC-20 token lists; FIFO + Average Cost on transaction history

  • Multi-currency display (USD/EUR/GBP/HUF); CoinGecko + Jupiter spot and historical prices

  • Time-windowed PnL (--timeframe=24h|7d|30d|ytd)

  • 428-test suite; runs under plain Node or Bun

  • Read-only & local-first: no orders/withdrawals/transfers; 4 of 6 connectors need only a public address; secrets live in your OS keychain and never enter the model's context β€” see SECURITY.md

See ROADMAP.md for what's done, what's next, and what's intentionally out of scope.

What it does

Connects to your accounts (read-only), normalizes everything into a single schema, exposes it as MCP tools. Then you ask Claude (or any MCP host):

  • "What do I own?"

  • "How is my portfolio split between crypto and prediction markets?"

  • "Show my Polymarket positions grouped by event."

  • "Refresh Bybit and tell me my BTC P&L."

The AI host generates the chart, the table, the breakdown. You don't build a UI.

Try it in 60 seconds (no API keys)

The zero-setup version β€” one command, no accounts, no keys, not even an address. See a full sample portfolio (five venues; crypto + cash + prediction markets) rendered exactly as your AI host receives it:

Copy & paste β€” that's it
npx headless-tracker demo
Copy & paste β€” that's it
account symbol class qty value price
────────────────────── ─────────────────── ────────── ──────── ─────── ───────
bybit:UNIFIED BTC crypto 0.420000 $25704 $61200
binance:spot SOL crypto 95.0000 $14440 $152.00
metamask:0xd8d2…f1a3 WBTC crypto 0.150000 $9150 $61000
solana:7vfC…Wd9k JUP crypto 1800.00 $1656 $0.9200
polymarket:0x9c1a…7b20 RATE-CUT-2026 (YES) prediction 1500.00 $930.00 $0.6200
…
Total: $104126 (15 positions across 5 venues)

Allocation by asset class:
 crypto $88024 84.5% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
 cash $14900 14.3% β–ˆβ–ˆβ–ˆ
 prediction $1202 1.2% β–ˆ

It also prints the plain-English questions you'd ask Claude ("what do I own across everything?", "how is it split?") mapped to the MCP tool that answers each. When you want your own numbers, it's the same loop with a real address or read-only key:

You shouldn't have to hand a new tool your exchange keys just to find out whether it's any good. Solana, Hyperliquid, and Polymarket read public on-chain addresses, so you can point HeadlessTracker at any wallet you can see (your own included) with zero credentials. (Hyperliquid is fully keyless β€” perp positions, account equity, and spot balances all read from just the address you trade from.)

Copy & paste β€” that's it
# install (or prefix any command with `npx`)
npm install -g headless-tracker

# add a public Solana wallet: no API key, just the address
headless-tracker setup solana
# Solana address (base58): 
# (press ENTER through the optional RPC + dust prompts)

# print the holdings right in your terminal, no Claude required
headless-tracker show holdings
Copy & paste β€” that's it
account symbol class qty value price
───────────────── ────── ────── ──────── ─────── ────────
solana:7Xk2…q9Fa SOL crypto 12.4081 $2604 $209.88
solana:7Xk2…q9Fa USDC crypto 540.0000 $540.00 $1.00
solana:7Xk2…q9Fa JUP crypto 1200.00 $612.00 $0.5100

Total: $3756 (3 positions across 1 accounts)

(Example output; the account id is shortened here for width. Your numbers come from the live chain.)

That is the whole loop: install, point at a public address, see normalized holdings. When you want your private accounts (Bybit, Binance), setup those too. Every connector uses read-only credentials, kept in your OS keychain, never written to disk and never sent anywhere except the exchange's own API. Then wire it into Claude and ask "what do I own?" to get the same data as a chat-native dashboard.

Non-interactive setup (scripts, Docker, CI)

setup also runs without prompts β€” pass flags, and keep any secret in an environment variable (never on the command line, so it stays out of your shell history):

Copy & paste β€” that's it
# public-address connectors: everything via flags, zero secrets
headless-tracker setup solana --address= --dust=0.5
headless-tracker setup hyperliquid --address=0x... # perp + spot, no key
headless-tracker setup polymarket --proxy-wallet=0x... 

# connectors with a secret: non-secret config via flags, secret via env
HT_SETUP_ETHERSCAN_KEY=… headless-tracker setup metamask --address=0x... --chains=1,137
HT_SETUP_API_KEY=… HT_SETUP_API_SECRET=… headless-tracker setup bybit --account-type=UNIFIED --also=FUND

Headless / no OS keychain (Docker, WSL, many Linux servers, CI): there's no Secret Service to write to, so setup registers the account and prints the exact HEADLESS_TRACKER_<CONNECTOR>_<ACCOUNT> env var to set with a JSON credential object β€” e.g. HEADLESS_TRACKER_SOLANA_<ADDR>='{"address":"…","dustThresholdUsd":0.5}'. Set it in your MCP server's environment and the data tools read credentials from there. Nothing is ever written to disk.

Interactive dashboard (live UI panel)

For hosts that support MCP Apps β€” Claude Desktop, ChatGPT, Goose, VS Code β€” say:

Show my dashboard

The host renders a sandboxed iframe in the chat panel with three live tabs:

  • Portfolio β€” total value KPIs, top positions table, allocation-by-symbol donut (top 7 + "Other" tail), warnings + failures

  • Weekly β€” 7-day window delta KPIs, recent trades table, skipped-symbols disclosure (with reasons)

  • Risk β€” concentration audit (single-position, venue, stablecoin reserve, prediction-market overweight) scored PASS / WARN / ALERT, by-venue donut

Plus a currency switcher (USD / EUR / GBP / HUF) and a refresh button. The iframe makes its own follow-up tool calls as the user clicks tabs β€” no extra prompting needed once it's open. Optional args:

Open the dashboard in HUF, weekly tab

Implementation: src/mcp/apps/dashboard/ (browser-side TS bundled into a single dist/mcp-apps/dashboard.html via bun run build:apps, ships with the package). The bundled artifact ships inside the npm package so users running npx headless-tracker don't need a build step.

If your host doesn't render MCP Apps yet, the render_dashboard tool still returns a textual confirmation. Use the prompt cookbook below as a fallback β€” same workflows, same data, just no live UI panel.

Prompt cookbook

Copy-paste these into any MCP-aware client. Each one expects the headless-tracker MCP server to be configured. None of them require new code on the server side.

Quick "where do I stand"

Build me a complete portfolio dashboard. Call get_holdings, get_allocations (by asset_class and by symbol), get_pnl, and get_polymarket_positions in parallel and synthesize a single dashboard artifact. Show top 10 positions, asset-class breakdown, total PnL. Be honest about NULL fields β€” don't fabricate.

Quick "how was this week"

Give me a 7-day review. Call get_pnl with timeframe=7d, get_holdings, and get_transactions with since=7d. Surface windowDelta with the approximation caveat ("current basket at historical prices, not trades within the window"), list the trades by exchange, and end with one short observation about what drove the change.

Quick "should I be worried"

Risk check my portfolio. Call get_holdings and get_allocations (by symbol, by asset_class, by connector). Score each: single-position concentration (ALERT > 40%), venue concentration (ALERT > 70%), stablecoin reserve (WARN < 5%, ALERT = 0%), prediction-market overweight (WARN > 15%). Output as a markdown table.

Tax season

I need to do my taxes. Call get_transactions for the past year (since=365d). Then call get_pnl with include_history=true and method=fifo. Group realized PnL by symbol and by month. Flag any sales with unknown cost basis (deposits / transfers without price) β€” those are honest gaps I'll have to research separately.

HUF view (or EUR / GBP)

Show my portfolio in Hungarian forint. Call get_holdings with currency=HUF. Sort by value descending. Sum the total in HUF and tell me whether the FX source was the live API or the static fallback.

Polymarket bet review

Walk me through my Polymarket positions. Call get_polymarket_positions with group_by_event=true. Then call get_pnl with include_history=true to get realized PnL via FIFO over /trades. For each event, show: title, my outcome holdings, current value, realized PnL so far, and end date. Flag any redeemable positions I should claim.

Quick portfolio queries from the CLI (no Claude required)

For the 3-second "what's in my portfolio?" question without opening Claude Desktop:

Copy & paste β€” that's it
headless-tracker show holdings
headless-tracker show pnl
headless-tracker show transactions --since=7d

Each prints a text table. Filters work: show holdings --account-id=bybit:UNIFIED, show holdings --asset-class=crypto, show transactions --since=24h --account-id=metamask:0xabc.

Multi-currency display

show holdings defaults to USD. Pass --currency= for live FX-converted values:

Copy & paste β€” that's it
headless-tracker show holdings --currency=HUF
headless-tracker show holdings --currency=EUR

FX rates come from a free public API (exchangerate-api.com) with frankfurter.dev as fallback, plus a static fallback if both fail (which surfaces as a warning so you know the displayed numbers may be a few percent stale). Supported: USD, EUR, GBP, HUF.

Cost basis methods (FIFO vs Average)

For honest realized P&L based on your transaction history (not connector metadata which can mix realized + unrealized for prediction markets):

Copy & paste β€” that's it
headless-tracker show pnl --include-history=true
headless-tracker show pnl --include-history=true --method=average

--method=fifo (default): consumes the oldest lot first per sell. --method=average: pools all priced acquisitions; sells out at the running weighted average.

Both methods preserve the "honest unknown" rule: tokens received via wallet transfer-in (no price) produce realizedPnl: null for any sell drawing from them β€” NOT a fabricated number. The realized PnL counts only sales whose every consumed lot had a known cost basis.

Time-windowed PnL

Copy & paste β€” that's it
headless-tracker show pnl --timeframe=7d
headless-tracker show pnl --timeframe=24h
headless-tracker show pnl --timeframe=ytd

Values your current basket at historical CoinGecko prices and reports the delta vs. now. Approximation: it does NOT account for trades within the window β€” it answers "if I held this exact basket N days ago, how much have I gained?" Polymarket positions and crypto without a CoinGecko mapping are skipped (counted in skippedSymbols). CoinGecko free-tier historical is daily granularity, so --timeframe=24h is "yesterday's close vs now".

Custom ERC-20 tokens

The bundled MetaMask token list covers USDC, USDT, WETH, WBTC, LINK, DAI. To track project-specific tokens:

Copy & paste β€” that's it
headless-tracker token add metamask:0xabc 1 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 USDC 6
headless-tracker token list
headless-tracker token remove metamask:0xabc 1 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48

Custom tokens are stored per-account in the SQLite account store (NOT the keychain β€” they're public on-chain identifiers, not secrets).

Supported integrations

Connector Auth Status Notes Bybit V5 API key + secret βœ“ Full UNIFIED / SPOT / CONTRACT / FUND accounts. Read-only key. Binance API key + secret βœ“ Holdings Spot account (/api/v3/account) + optional Futures wallet/positions (/fapi/v2/account). Read-only key ("Enable Reading" only). Stablecoins priced at $1; non-stable assets priced via batch /api/v3/ticker/24hr?type=MINI. Open futures positions surface as separate Holdings with side/leverage/PnL/liquidation in metadata. Tx history is ok([]) for v0.13 β€” deferred. binance.com only (binance.us deferred). MetaMask / EVM wallets Etherscan V2 API key βœ“ Full Single key covers Ethereum, Polygon, BSC, Base, Arbitrum, Optimism. Native + bundled common ERC-20 tokens (USDC, USDT, WETH, WBTC, LINK, DAI) for balances; native + ERC-20 transfers for transactions. Custom token lists via headless-tracker token add .... Multi-wallet per account (one Etherscan key, multiple addresses). BSC/Base require Etherscan Pro on the free tier (auto-skipped with a warning otherwise). Polymarket Proxy wallet address (no API key) βœ“ Full Uses public data-api. Positions + BUY/SELL trade history (up to ~1000 most recent) via /trades?user=PROXY. Settled-loss positions (resolved markets worth $0 that the data-api still returns) are filtered out by a value-based dust threshold (dustThresholdUsd, default $0.01). Solana wallets Base58 address (no API key) βœ“ Holdings Public Solana RPC + Jupiter Price API v2. Native SOL + SPL tokens (Token program v1; Token-2022 deferred). Multi-wallet per account, optional premium RPC URL (Helius/QuickNode/Triton) for users tracking 3+ wallets. Pinned metadata for major mints (USDC/USDT/mSOL/JUP/JTO/PYTH/BONK/RNDR/WIF/JLP). Tx history is ok([]) for v0.12 β€” coming in v0.14 with premium-RPC opt-in. Hyperliquid EVM address (no API key, no signature) βœ“ Full Public info endpoint. Perp account equity reported as the account's net USD value (collateral + unrealized PnL); open perp positions surface as separate Holdings with signed size, notional, unrealized PnL, entry/liquidation price and leverage in metadata β€” their notional is deliberately not summed into net worth (would overstate a leveraged account). Spot balances priced via spotMetaAndAssetCtxs USDC pairs; USDC = $1, unpriceable tokens dust-filtered. Tx history via recent fills (userFills, up to 2000). Multi-address per account.

To add a new connector, implement Connector from src/connectors/types.ts and add it to CONNECTOR_FACTORIES in src/mcp/orchestrator.ts. ~150-400 lines of code per connector based on the existing six (depends on whether the upstream API is REST/SDK/RPC and how rich the response shape is).

MCP tools exposed

Tool Purpose Common prompts get_holdings Current holdings across all accounts "what do I own", "show my portfolio", "current positions" get_pnl Aggregate profit/loss summary "how am I doing", "what's my P&L", "am I up or down" get_polymarket_positions Polymarket-specialized, event-grouped "show my Polymarket bets", "election bets" get_transactions Transaction history with since filter "show my recent trades", "transactions this week" get_allocations Group-by breakdown (asset class / connector / chain / symbol) "how is my portfolio split", "biggest positions" refresh_data Force cache invalidation "refresh", "get the latest", "fetch now"

The data tools accept an optional account_id filter (e.g. metamask:0xabc..., bybit:UNIFIED). Without a filter, they query everything.

Account and setup management (all credential writes are read-only API keys stored in the OS keychain):

Tool Purpose setup_connector Configure a connector by writing read-only credentials to the OS keychain list_accounts List configured accounts without exposing credentials add_wallet_address Add another wallet address to an existing MetaMask or Solana account remove_account Delete an account and its credentials from the keychain add_custom_token Track a project-specific ERC-20 token on a MetaMask account remove_custom_token Stop tracking a custom ERC-20 token (public on-chain data, no keychain) list_custom_tokens List the custom ERC-20 tokens tracked per MetaMask account

MCP App panels (interactive UI rendered in the chat):

Tool Purpose render_dashboard Interactive dashboard panel: holdings, P&L, allocations, prediction markets render_settings Settings panel: a GUI alternative to the CLI setup flow

Why local-first

  • API keys never leave your machine. Stored in your OS keychain via @napi-rs/keyring.

  • Cache is local SQLite (the runtime's built-in driver: node:sqlite under Node, bun:sqlite under Bun). No server, no SaaS, no analytics pings.

  • No telemetry by default. Error reporting is strictly opt-in: it only does anything if you set a SENTRY_DSN, and even then it never sends portfolio data β€” no amounts, balances, wallet addresses, API keys, or account labels, only the error class and a scrubbed message/stack plus which connector failed. See decisions.md (2026-06-06) for the details.

  • Read-only by design. No transaction signing. Nothing this tool can do can lose your money.

  • Per-connector cache TTL (crypto wallets 60s, exchanges 120s, Polymarket 30s) keeps things fast without hammering upstream APIs.

Architecture

Copy & paste β€” that's it
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ headless-tracker β”‚
 β”‚ (Node or Bun) β”‚
 β”‚ ──────────────────────── β”‚
 β”‚ src/connectors/ β”‚
 β”‚ bybit.ts β”‚
 β”‚ metamask.ts β”‚
 β”‚ polymarket.ts β”‚
 β”‚ src/types.ts (schema) β”‚
 β”‚ src/cache.ts (SQLite) β”‚
 β”‚ src/vault.ts (keyring) β”‚
 β”‚ src/accounts.ts (registry)β”‚
 β”‚ src/mcp/orchestrator.ts β”‚ ← parallel fan-out + in-flight Promise dedup
 β”‚ src/mcp/server.ts β”‚ ← McpServer + 15 tools
 β”‚ β–² β”‚
 β”‚ β”‚ stdio MCP β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 β”‚
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚ β”‚ β”‚
β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
β”‚Claude Desktopβ”‚ β”‚Claude Codeβ”‚ β”‚ Cursor/Codexβ”‚
β”‚ ChatGPT β”‚ β”‚ β”‚ β”‚ ZED, etc. β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Sample prompts and responses

The point of headless-tracker is that you don't write SQL or learn a CLI β€” you ask Claude. Some example sessions:

"What do I own?"

Claude calls get_holdings({}) and returns a formatted breakdown: "You currently hold 0.5 BTC ( $30,000), 2 ETH ( $5,000) on Bybit, plus 1 ETH on your MetaMask wallet, and one Polymarket position on the 2024 election worth $60. Total portfolio value: ~$35,060."

"Show my Polymarket bets grouped by event."

Claude calls get_polymarket_positions({ group_by_event: true }) and renders an event-grouped table with title, your Yes/No outcome holdings, total event value, and combined cash P&L per event.

"How am I split between crypto and prediction markets?"

Claude calls get_allocations({ by: "asset_class" }) and returns a percentage breakdown: "97.5% crypto ($35,000), 2.5% prediction ($1,000)."

"Refresh my data and show the latest holdings."

Claude calls refresh_data({}) then get_holdings({}) β€” the cache is invalidated, fresh data is fetched from all upstream APIs in parallel, and Claude renders the new state.

"Give me a complete portfolio dashboard."

Claude calls multiple tools in parallel (get_holdings, get_allocations, get_pnl, get_polymarket_positions) and synthesizes a multi-section dashboard. The orchestrator's in-flight Promise dedup ensures each connector is hit at most once even when fan-out is wide.

Development

Building from source uses Bun 1.3+. End users don't need this β€” see Quick start .

Copy & paste β€” that's it
git clone https://github.com/tamasPetki/HeadlessTracker.git
cd headless-tracker
bun install
bun test # 377 tests, ~5s
bun run typecheck # bun --bun tsc --noEmit
bun run build:apps # bundle the dashboard MCP App into dist/mcp-apps/
bun run build # build the Node-runnable dist/ (what npm ships)
bun run start # start MCP server on stdio (debug only)
bun run setup bybit # interactive credential setup

To add a connector, follow the existing pattern in src/connectors/. The Connector interface enforces uniform Result<T> error handling across all integrations β€” there is no exception-throwing path for expected failures (auth, rate limit, network).

Related

bulltrapp.com β€” a hosted web portfolio tracker by the same maintainer. Same problem space, different surface. Use either or both.

License

MIT