Skip to main content
Glama
README.md
# zapper-mcp

An MCP server that exposes the [Zapper](https://zapper.xyz) 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`](./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](#known-limitations).

---

## Requirements

- Node.js 20+
- pnpm
- A [Zapper API key](https://build.zapper.xyz)

---

## Install

```bash
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:

```bash
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):

```bash
ZAPPER_API_KEY=your_key pnpm client
```

Output: lists tools/resources/prompts, then calls each tool against `vitalik.eth`.

**Direct server start:**

```bash
ZAPPER_API_KEY=your_key pnpm start
```

---

## Claude Desktop wiring

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```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.

TDQS

A4.2/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct aspect of wallet data: token balances, DeFi positions, or full portfolio. Descriptions clearly differentiate them, leaving no ambiguity for an agent.

Naming Consistency5/5

All tools follow a consistent 'get_<descriptive_noun>' pattern (get_app_positions, get_portfolio, get_token_balances), making naming predictable and readable.

Tool Count5/5

Three tools is well-scoped for a wallet data server, covering the core needs without excess or deficiency.

Completeness4/5

The set covers token balances, DeFi positions, and a combined portfolio, which forms a complete picture for most wallet queries. Missing advanced features like transaction history are acceptable for the scope.

Maintenance

ActivityInactive
ResponsivenessNo issues