loading…
Search for a command to run...
loading…
An MCP server that exposes the Zapper DeFi portfolio API, enabling LLMs to query wallet balances, token holdings, and DeFi positions using natural language.
An MCP server that exposes the Zapper DeFi portfolio API, enabling LLMs to query wallet balances, token holdings, and DeFi positions using natural language.
An MCP server that exposes the Zapper DeFi portfolio API as a thoughtfully designed tool surface for LLM clients. Connect it to Claude Desktop or any MCP-compatible host and ask natural-language questions about any wallet — "what is this wallet worth?", "does it have any Aave positions?", "show me the top holdings on Base."
Built on Day 9 of a 21-day AI engineering sprint. Day 10 wires this server into a Mastra agent.
The design rationale for each primitive is in DESIGN.md. The short version:
| Primitive | Name | Why this placement |
|---|---|---|
| Tool | get_portfolio |
Model-invoked, dynamic per address, returns full token + DeFi breakdown |
| Tool | get_token_balances |
Focused tool for spot-token questions; avoids making the model parse a full portfolio when it only needs token holdings |
| Tool | get_app_positions |
Focused tool for DeFi questions; separate from get_portfolio so the model can express precise intent and receive a focused schema |
| Resource | zapper://supported-networks |
Static network list — host injects it as ambient context at prompt-assembly time so the model knows valid network names without burning a tool-call turn |
| Prompt | analyze-wallet |
User-invoked workflow that pre-seeds a multi-turn portfolio analysis conversation with analyst persona, tool inventory, and wallet address |
Why not one big get_everything tool? Collapsing the tools would force the model to receive and parse a large mixed-schema response for every question, even focused ones. A tool boundary is a declaration of scope — the right tool returns exactly what the reasoning step needs.
Why is the API key in server config, not a tool argument? Credentials belong in the host layer (env vars injected at process spawn), not in the MCP protocol. If api_key were a tool parameter, it would flow through the LLM's reasoning and appear in conversation history. For a multi-tenant deploy the right mechanism is transport-layer auth (Bearer token over Streamable HTTP) or per-user OAuth — both out of scope here. See Known limitations.
git clone https://github.com/mehdi-loup/zapper-mcp
cd zapper-mcp
pnpm install
pnpm build
Copy .env.example to .env and add your key:
cp .env.example .env
# edit .env and set ZAPPER_API_KEY=your_key_here
The server fails fast at boot if ZAPPER_API_KEY is missing — you'll see the error immediately, not on the first tool call.
Standalone smoke test (confirms everything works without Claude Desktop):
ZAPPER_API_KEY=your_key pnpm client
Output: lists tools/resources/prompts, then calls each tool against vitalik.eth.
Direct server start:
ZAPPER_API_KEY=your_key pnpm start
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"zapper-mcp": {
"command": "node",
"args": ["/absolute/path/to/zapper-mcp/build/server.js"],
"env": {
"ZAPPER_API_KEY": "your_key_here"
}
}
}
}
Restart Claude Desktop. The three tools, the zapper://supported-networks resource, and the analyze-wallet prompt will be available.
Logs (if the server fails to load):
~/Library/Logs/Claude/mcp-server-zapper-mcp.log
To wire this server into a Mastra agent via Mastra's MCP client:
node /path/to/build/server.jszapper-mcplib/zapper.ts in the agent repo becomes unusedNot all tools need to be exposed to the Mastra agent; that's a Day 10 design call.
get_portfolio(address, networks?)Full portfolio breakdown: total USD, all token holdings, all DeFi positions.
address — wallet address or ENS name
networks — optional array: ["ethereum", "base", "arbitrum", ...]
get_token_balances(address, networks?)Spot token balances only (no DeFi positions).
get_app_positions(address, networks?, app_slug?)DeFi app positions only (Aave, Uniswap, Sablier, etc.).
app_slug — optional filter: "aave-v3", "uniswap-v3", ...
zapper://supported-networksJSON array of { name, chainId } for all indexed networks. Read by host at context-assembly time.
analyze-walletPre-seeds a portfolio analysis conversation. Takes an address argument.
Every tool returns isError: true with a model-actionable message on:
An empty wallet (totalUSD: 0, tokens: []) returns isError: false — empty is not an error.
ZAPPER_API_KEY and serves one owner. A multi-tenant deploy needs per-user OAuth or transport-layer auth (Streamable HTTP with Bearer tokens).resources/subscribe: zapper://supported-networks is a static list. Live updates would require the server to advertise subscribe capability and emit notifications/resources/updated.Day 10: wire this server into the Mastra wallet agent at ../day1-wallet-agent/ via Mastra's MCP client. The agent will consume Zapper data exclusively through MCP, validating that the tool surface actually decouples the capability from the agent framework.
Add this to claude_desktop_config.json and restart Claude Desktop.
{
"mcpServers": {
"zapper-mcp": {
"command": "npx",
"args": []
}
}
}