mehdi-loup

zapper-mcp

Community mehdi-loup
Updated

MCP server exposing the Zapper DeFi portfolio API as model-friendly tools

zapper-mcp

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.

Tool surface

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.

Requirements

Install

git clone https://github.com/mehdi-loup/zapper-mcp
cd zapper-mcp
pnpm install
pnpm build

Configuration

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.

Run

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

Claude Desktop wiring

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

Mastra integration (Day 10)

To wire this server into a Mastra agent via Mastra's MCP client:

  1. Start the server: node /path/to/build/server.js
  2. Configure the Mastra MCP client with stdio transport, server name zapper-mcp
  3. The agent consumes Zapper data exclusively through MCP — lib/zapper.ts in the agent repo becomes unused

Not all tools need to be exposed to the Mastra agent; that's a Day 10 design call.

Tool reference

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", ...

Resource: zapper://supported-networks

JSON array of { name, chainId } for all indexed networks. Read by host at context-assembly time.

Prompt: analyze-wallet

Pre-seeds a portfolio analysis conversation. Takes an address argument.

Error handling

Every tool returns isError: true with a model-actionable message on:

  • HTTP 401 / invalid API key
  • HTTP 429 / rate limited
  • HTTP 5xx / Zapper server error
  • Network timeout (15s)
  • Malformed response

An empty wallet (totalUSD: 0, tokens: []) returns isError: false — empty is not an error.

Known limitations

  • Single-key trust model: the server holds one ZAPPER_API_KEY and serves one owner. A multi-tenant deploy needs per-user OAuth or transport-layer auth (Streamable HTTP with Bearer tokens).
  • No caching: every tool call hits the Zapper API. A production server would add a short TTL cache (positions change slowly) and respect rate limits proactively.
  • No resources/subscribe: zapper://supported-networks is a static list. Live updates would require the server to advertise subscribe capability and emit notifications/resources/updated.
  • stdio transport only: Streamable HTTP transport deferred to a future iteration.
  • Pagination ceiling: tools return up to 50 tokens and 20 app positions per request.

What's next

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.

MCP Server · Populars

MCP Server · New