Crypto Portfolio MCP
Crypto Portfolio MCP
Track, analyze, and optimize your crypto portfolio directly inside Claude — multi-exchange, multi-coin, multi-wallet, with live prices and unrealized P&L.
MCP server (Model Context Protocol) that turns Claude into your personal crypto portfolio analyst. Connect it once and ask natural questions like "What's my BTC up by since I bought?" or "How would my net worth have looked during the May crash?" — Claude calls these tools under the hood.
What it does
A Claude-native portfolio tracker that:
Records holdings across exchanges and wallets (Coinbase, Binance, Kraken, MetaMask, Ledger, anything)
Calculates real P&L — current value, cost basis, unrealized gain/loss in USD and %, allocation breakdown
Streams live prices from CoinGecko (Demo tier, free)
Reconstructs historical value — what was your portfolio worth on March 14, 2024? Now you can ask
Surveys the market — BTC dominance, trending coins, top-10 movers — without leaving the chat
It's the data layer for the companion Crypto Tax MCP, but works fully standalone.
Who it's for
Persona | Why they use it |
Multi-exchange holders | One place to ask "what am I actually worth?" instead of opening five apps |
DeFi/Layer-1 explorers | Track holdings the exchange dashboards don't see (cold storage, hardware wallets) |
Active traders | Quick P&L checks during a session without breaking flow |
Tax prep | Feed the data into Crypto Tax MCP at year-end for 1099-DA reconciliation |
Claude power users | You already chat with Claude all day — your portfolio should live there too |
Tools (7)
Tool | Purpose |
| Total value + per-asset P&L + allocation % |
| Live price for 1–50 coins |
| Record a purchase (coin, qty, price, date, exchange) |
| Remove a holding (all entries or one exchange) |
| Daily portfolio value over 1–365 days |
| Resolve name/symbol → CoinGecko ID |
| Market cap, BTC dominance, trending, top-10 |
Example conversations
These are the real prompts users say to Claude — what happens behind the scenes is shown in italics.
1. First-time setup: record a holding
You: "I bought 0.5 BTC on Coinbase on Jan 15, 2024 at $42,000."
Claude calls: search_coin("BTC") → finds bitcoin → add_holding({coin_id: "bitcoin", symbol: "BTC", quantity: 0.5, purchase_price_usd: 42000, purchase_date: "2024-01-15", exchange: "Coinbase"})
Claude: "Done. Tracking 0.5 BTC purchased Jan 15, 2024 at $42,000 on Coinbase ($21,000 cost basis)."
2. Check what you're worth right now
You: "What's my portfolio worth?"
Claude calls: get_portfolio_summary({currency: "usd"})
Claude: "Your portfolio is worth $73,420 across 5 assets, up +$18,920 (+34.7%) vs cost basis.
BTC $48,200 (65.6%) — +$27,200 / +129% ↑
ETH $15,100 (20.6%) — −$2,100 / −12% ↓
SOL $6,800 (9.3%) — +$3,800 / +127% ↑
USDT $2,500 (3.4%) — flat
MATIC $820 (1.1%) — −$180 / −18% ↓"
{
"total_value": 73420.00,
"total_cost_basis": 54500.00,
"total_unrealized_pnl_usd": 18920.00,
"total_unrealized_pnl_pct": 34.72,
"currency": "usd",
"asset_count": 5,
"holdings": [
{
"coin_id": "bitcoin",
"symbol": "BTC",
"quantity": 0.5,
"current_price": 96400,
"value": 48200,
"exchange": "Coinbase",
"purchase_price_usd": 42000,
"cost_basis": 21000,
"unrealized_pnl_usd": 27200,
"unrealized_pnl_pct": 129.52,
"price_change_24h_pct": 2.4,
"allocation_pct": 65.65
}
]
}3. Quick price check (no holding needed)
You: "What's the price of SOL and AVAX right now?"
Claude calls: get_asset_price({coins: ["solana", "avalanche-2"]})
Claude: "SOL is at $185.40 (+3.2% 24h, $84B mcap). AVAX is at $42.10 (−1.8% 24h, $17B mcap)."
4. Find the right coin ID
You: "I want to add my Polygon holdings. What's the CoinGecko ID?"
Claude calls: search_coin({query: "polygon"})
Claude: "Polygon is now branded as POL with ID
polygon-ecosystem-token. The old MATIC token ismatic-network. Which one do you hold?"
5. Add a wallet holding (no price/date)
You: "Add 12 ETH in my Ledger cold storage. I don't remember the buy price."
Claude calls: add_holding({coin_id: "ethereum", symbol: "ETH", quantity: 12, exchange: "Ledger"})
Claude: "Added 12 ETH on Ledger. No cost basis recorded — current value will show, but unrealized P&L will be null. Add a purchase price later with
add_holdingif you want gain tracking."
6. Visualize the portfolio over time
You: "Show me how my portfolio performed over the last 90 days."
Claude calls: get_portfolio_history({days: 90})
Claude: "Your portfolio over 90 days:
Day 1 (Mar 10): $58,200
Peak (Apr 22): $81,400
Trough (May 15): $52,100
Today (Jun 8): $73,420
Net change: +$15,220 (+26.2%). You weathered the May correction and have recovered ~89% of the peak."
{
"currency": "usd",
"days": 90,
"data_points": 90,
"history": [
{"date": "2026-03-10", "value": 58200.00},
{"date": "2026-03-11", "value": 59100.00},
"...",
{"date": "2026-06-08", "value": 73420.00}
]
}7. "What's everyone talking about?"
You: "Give me a market snapshot — what's hot right now?"
Claude calls: get_market_overview()
Claude: "Total crypto mcap: $3.2T (−1.2% 24h). BTC dominance: 56.8%. ETH: 14.2%.
Trending now: Hyperliquid (HYPE), Bittensor (TAO), Ondo (ONDO), Sui (SUI), Toncoin (TON).
Top 10: BTC $96.4K, ETH $3.4K, USDT (stable), BNB $710, SOL $185, USDC (stable), XRP $2.40, DOGE $0.42, ADA $1.10, AVAX $42."
8. Remove a holding
You: "I sold all my MATIC on Coinbase, remove it."
Claude calls: remove_holding({coin_id: "matic-network", exchange: "Coinbase"})
Claude: "Removed MATIC on Coinbase from your portfolio."
9. Multi-exchange same coin
You: "I have 0.3 BTC on Kraken and 0.2 BTC on Binance. Add both."
Claude calls add_holding twice with different exchange values.
Claude: "Tracking 0.5 BTC total across two exchanges (0.3 Kraken, 0.2 Binance).
get_portfolio_summarywill show them as separate rows so you can see exchange-level exposure."
10. Cost basis correction
You: "I made a typo earlier — my SOL buy price was $95, not $195. Fix it."
Claude calls: add_holding({coin_id: "solana", symbol: "SOL", quantity: <same>, purchase_price_usd: 95, ...}) — upsert merges by (user, coin, exchange).
Claude: "Updated. Your SOL cost basis is now $95/coin. Recalculated unrealized P&L: +$4,500 (was −$5,500 with the wrong price)."
Pricing tiers
Tier | Holdings | History | Price |
Free | 3 | 30 days | $0 |
Pro | Unlimited | 365 days | $19/mo |
Free tier is fully functional for casual portfolios. Pro removes both limits — that's all.
Stack
Runtime: Node.js 20, TypeScript
MCP SDK:
@modelcontextprotocol/sdk(Streamable HTTP transport)Framework: Express + Zod schemas
Prices: CoinGecko Demo API — 30 req/min, 10K req/month, free. 60s response cache means a 10-user free portfolio uses ~80% fewer calls than naive fetching.
Storage: SQLite via
better-sqlite3(single file, no external DB)Auth: Bearer API key (SHA-256 hashed; raw key never persisted) + MCPize upstream proxy passthrough (
X-MCPize-Customer-Id+X-MCPize-Tier)
Development
# Clone + install
git clone https://github.com/biggling/crypto-portfolio-mcp.git
cd crypto-portfolio-mcp
npm install
# Configure
cp .env.example .env
# Edit .env — set COINGECKO_API_KEY from https://www.coingecko.com/en/api
# Run in dev (watch mode)
npm run dev
# Build + run production
npm run build
npm startEnvironment variables
Var | Purpose | Default |
| HTTP listen port |
|
| Demo-tier key from coingecko.com | required |
| SQLite file path |
|
|
|
|
| Upstream proxy token for MCPize gateway | optional |
Create a test API key
node -e "
const db = require('better-sqlite3')('./data/holdings.db');
const crypto = require('crypto');
const key = 'pk_test_' + crypto.randomBytes(24).toString('hex');
const hash = crypto.createHash('sha256').update(key).digest('hex');
db.prepare('INSERT INTO api_keys (key_hash, tier) VALUES (?, ?)').run(hash, 'pro');
console.log('Test key:', key);
"Test it works
curl -X POST http://localhost:3000/mcp \
-H "Authorization: Bearer YOUR_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'You should see all 7 tools listed in the response.
Deployment
For production deployment (VPS + nginx + Let's Encrypt + systemd), use:
sudo bash deploy.sh "https://github.com/biggling/crypto-portfolio-mcp.git" "mcp.example.com"The script installs Node 20, builds, generates a systemd unit, and walks you through nginx + certbot. Verify with bash test-deployment.sh https://mcp.example.com YOUR_KEY.
Architecture
Claude AI (or MCPize gateway)
│ HTTPS Bearer <key>
▼
nginx reverse proxy ──► proxy_buffering off, 300s timeouts (Streamable HTTP needs this)
│ :3000
▼
crypto-portfolio-mcp (Node.js / Express)
├─► SQLite ── api_keys, holdings
└─► CoinGecko ── 60s in-memory cacheAuth flow
Client sends
Authorization: Bearer <key>(direct buyer) orAuthorization: Bearer <upstream-token>+X-MCPize-Customer-Id+X-MCPize-Tier(via MCPize gateway).Server identifies user via SHA-256 hash — the plaintext key is never stored.
Tools receive
userId(the hash) andtier(free|pro) in context.Tier limits enforced inside each tool: free tier caps holdings at 3 and history at 30 days.
Roadmap
Phase 1 (now): Portfolio P&L tracker — ✅ shipped
Phase 2: Crypto Tax MCP (FIFO/LIFO/HIFO lot matching, 1099-DA reconciliation) — ✅ shipped (companion repo)
Phase 3: OAuth 2.1 PKCE auth for the Anthropic directory
Phase 4: Polar.sh / Stripe billing for direct buyers
Phase 5: Multi-chain wallet sync (Moralis, Etherscan, on-chain holdings auto-discovery)
License
MIT — do whatever, but no warranty.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/biggling/crypto-portfolio-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server