|
| 1 | +--- |
| 2 | +title: "Building a Production-Grade MCP Server for the Cardano Blockchain" |
| 3 | +description: "How we gave AI agents deep, idiomatic access to Cardano — UTxOs, native assets, smart contracts, indexers, and full CIP-1694 governance — without leaving the editor." |
| 4 | +authors: [emmanuel] |
| 5 | +tags: [cardano, mcp, ai, governance, developer-tools] |
| 6 | +date: 2026-06-08 |
| 7 | +--- |
| 8 | + |
| 9 | +*How we gave AI agents deep, idiomatic access to Cardano — UTxOs, native assets, smart contracts, indexers, and full CIP-1694 governance — without leaving the editor.* |
| 10 | + |
| 11 | +<!-- truncate --> |
| 12 | + |
| 13 | +## Background |
| 14 | + |
| 15 | +Solana has 40+ Model Context Protocol servers. EVM chains have 30+. Cardano had nothing purpose-built. |
| 16 | + |
| 17 | +That gap matters more than it sounds. Cardano's eUTxO model is fundamentally different from every other major blockchain. There are no account balances — only UTxOs. Smart contracts are validators that approve or deny spending, not programs that run arbitrary code. Datums carry state attached to UTxOs, not to a contract address. Amounts are always in lovelace, not ADA. Generic blockchain MCPs get this wrong. They map Cardano onto an Ethereum mental model and produce incorrect, dangerous advice. |
| 18 | + |
| 19 | +So we built one specifically for Cardano: **[cardano/mcp](https://github.com/lidonation/Cardano-mcp)**. |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## What is MCP? |
| 24 | + |
| 25 | +The [Model Context Protocol](https://modelcontextprotocol.io) is an open standard from Anthropic that lets AI assistants like Claude call tools with structured inputs and outputs. Instead of copying-and-pasting data into a chat window, you register an MCP server and Claude can query live blockchain state, construct transactions, read governance proposals, and decode smart contract datums directly inside your coding session. |
| 26 | + |
| 27 | +An MCP server is a process that communicates over stdin/stdout (or SSE). It exposes a list of **tools** — each with a name, a Zod schema describing inputs, and a handler function. The AI picks the right tool, provides validated arguments, and gets back structured JSON. |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Architecture |
| 32 | + |
| 33 | +The server is written in strict TypeScript and organised into **6 modules**, each mapping to a distinct domain of Cardano development: |
| 34 | + |
| 35 | +``` |
| 36 | +src/ |
| 37 | +├── index.ts ← MCP entry point, registers all modules |
| 38 | +├── config.ts ← network config, env vars, lovelaceToAda helper |
| 39 | +├── lib/ |
| 40 | +│ ├── koios.ts ← Koios client with retry + exponential backoff |
| 41 | +│ ├── blockfrost.ts ← Blockfrost client with project-ID injection |
| 42 | +│ ├── maestro.ts ← Maestro client (fast UTxO queries) |
| 43 | +│ ├── kupo.ts ← Kupo sidecar client |
| 44 | +│ └── cbor.ts ← PlutusData encode/decode via CSL |
| 45 | +└── modules/ |
| 46 | + ├── query/ ← UTxOs, transactions, assets, blocks |
| 47 | + ├── tokens/ ← NFT metadata, wallet assets, minting, policies |
| 48 | + ├── txbuilder/ ← protocol params, minADA, payment tx, script tx, submit |
| 49 | + ├── contracts/ ← eUTxO explainer, Aiken docs, CBOR decode/encode |
| 50 | + ├── indexer/ ← address watching, Kupo/Yaci passthrough |
| 51 | + └── governance/ ← CIP-1694 proposals, DReps, committee, treasury, votes |
| 52 | +``` |
| 53 | + |
| 54 | +**38 tools across 6 modules.** Every tool input is validated by Zod with `.describe()` on every field so Claude gets accurate documentation. Every tool returns structured JSON in the MCP text envelope. |
| 55 | + |
| 56 | +### Multi-API strategy |
| 57 | + |
| 58 | +No single Cardano API does everything well, so we use three: |
| 59 | + |
| 60 | +| API | Strength | Used for | |
| 61 | +|-----|----------|----------| |
| 62 | +| **Blockfrost** | Reliable, fast, well-documented | Protocol params, assets, tx submission, governance | |
| 63 | +| **Koios** | Free, no auth, rich governance metadata (CIP-100) | DRep info, proposal metadata, vote rationales | |
| 64 | +| **Maestro** | Fastest UTxO queries | High-throughput address lookups | |
| 65 | +| **Kupo** | Local sidecar, event-based | Address watching, pattern matching | |
| 66 | + |
| 67 | +For governance specifically, we try Koios first (8 s timeout) to get rich on-chain metadata, and fall back to Blockfrost for basic data if Koios is slow. This is important because Koios returns the `meta_json` field from CIP-100/108 metadata anchors directly in the proposal listing, while Blockfrost requires additional round-trips. |
| 68 | + |
| 69 | +### The eUTxO mental model baked in |
| 70 | + |
| 71 | +Every tool in the `query` module works in lovelace internally. The `lovelaceToAda()` helper is the only place division by 1,000,000 happens. Tool descriptions explain why UTxOs, not balances, are the right abstraction. The `contracts` module includes an `explain_eutxo_model` tool that gives Claude a grounding document before it tries to reason about script spending conditions. |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## CIP-1694 Governance — the hardest part |
| 76 | + |
| 77 | +CIP-1694, Cardano's on-chain governance system, went live on mainnet in January 2025 with the Plomin Hard Fork. It introduces three voting bodies (DReps, SPOs, Constitutional Committee), seven action types, and a rich metadata standard (CIP-100/108) for attaching rationale to votes. |
| 78 | + |
| 79 | +The `governance` module exposes 12 tools: |
| 80 | + |
| 81 | +- `list_governance_proposals` — paginated list with titles and abstracts |
| 82 | +- `get_proposal_details` — full metadata for a single proposal |
| 83 | +- `get_proposal_votes` — aggregated vote tallies + per-voter breakdown by role |
| 84 | +- `get_drep_list`, `get_drep_info` — DRep registry and profiles |
| 85 | +- `get_committee_info` — Constitutional Committee composition |
| 86 | +- `get_treasury_balance` — live treasury reserves |
| 87 | +- `get_constitution` — current constitution hash and anchor |
| 88 | +- `get_proposal_sentiment` — AI-powered summary of DRep rationales |
| 89 | + |
| 90 | +### IPFS metadata enrichment |
| 91 | + |
| 92 | +Governance proposals anchor their metadata on IPFS using CIP-108 JSON-LD. When Koios returns a proposal without inline `meta_json`, we fetch the anchor URL ourselves through the IPFS gateway and extract `body.title`, `body.abstract`, `body.rationale`, and `body.motivation`. |
| 93 | + |
| 94 | +```typescript |
| 95 | +async function fetchIpfs<T>(ipfsUrl: string): Promise<T | null> { |
| 96 | + const cid = ipfsUrl.replace("ipfs://", ""); |
| 97 | + const res = await fetch(`${IPFS_GATEWAY}/${cid}`, { |
| 98 | + signal: AbortSignal.timeout(8000), |
| 99 | + headers: { Accept: "application/json" }, |
| 100 | + }); |
| 101 | + if (!res.ok) return null; |
| 102 | + return res.json(); |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +This turns a raw proposal hash into a readable proposal with a title and abstract — something no other Cardano API surface does automatically. |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +## The Demo App |
| 111 | + |
| 112 | +To show what you can build on top of the MCP server, we built a React demo application that queries live mainnet data through the same Blockfrost and Koios calls. The demo runs as two processes: |
| 113 | + |
| 114 | +- **Express bridge** (`server.ts`) — exposes MCP tool logic as REST `POST /tools/:toolName` endpoints |
| 115 | +- **Vite frontend** (`src/`) — React + Tailwind UI that proxies `/tools`, `/health`, and `/chat` to the bridge |
| 116 | + |
| 117 | +The bridge exists because MCP is a stdio protocol — you can't call it from a browser. Wrapping the same tool logic in an Express server gives us HTTP endpoints the frontend can hit directly. |
| 118 | + |
| 119 | +### Four tabs |
| 120 | + |
| 121 | +**Wallet** — Enter any Cardano address. The app fetches all UTxOs, sums lovelace, lists native assets with policy IDs and hex asset names, and shows the 20 most recent transactions. Addresses persist to `localStorage` so you don't have to re-enter on refresh. |
| 122 | + |
| 123 | +**Tokens** — Query any policy ID to see all assets minted under it, with on-chain NFT metadata fetched via the `get_nft_metadata` tool. |
| 124 | + |
| 125 | +**Governance** — The richest tab. Loads all active governance proposals with: |
| 126 | +- Title and abstract from CIP-100/108 IPFS metadata |
| 127 | +- Vote bar showing yes/no/abstain percentages |
| 128 | +- Per-role breakdown (DRep, SPO, Constitutional Committee) |
| 129 | +- Sample votes with IPFS rationale links |
| 130 | +- **AI sentiment button** — one click fetches up to 8 DRep rationale documents from IPFS, aggregates the vote tallies, and asks Claude Haiku to write a neutral 400-token summary of what the community has said |
| 131 | + |
| 132 | +**Network** — Epoch, slot height, block count, transaction count, and a full protocol parameters table with plain-English tooltips explaining what each parameter controls. |
| 133 | + |
| 134 | +### AI chatbot |
| 135 | + |
| 136 | +A floating **₳** button in the bottom-right corner opens a chat panel backed by Claude Haiku. On each message the server fetches live network stats and protocol parameters, prepends them as context, and passes the user's question (plus the last 8 turns of history) to the model. The result: a Cardano-aware assistant that can answer "what is the current min fee?" or "explain how DReps work" with up-to-date, grounded answers. |
| 137 | + |
| 138 | +```typescript |
| 139 | +// system prompt built at request time |
| 140 | +const systemPrompt = `You are a Cardano blockchain assistant with access to live mainnet data. |
| 141 | +
|
| 142 | +Current network state: |
| 143 | +- Epoch: ${networkInfo.epoch_no} |
| 144 | +- Block height: ${networkInfo.block_height} |
| 145 | +- Treasury balance: ${networkInfo.reserve} lovelace |
| 146 | +
|
| 147 | +Current protocol parameters: |
| 148 | +${JSON.stringify(protocolParams, null, 2)} |
| 149 | +
|
| 150 | +Answer concisely. Use lovelace for amounts and convert to ADA where helpful.`; |
| 151 | +``` |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## Running it yourself |
| 156 | + |
| 157 | +### Prerequisites |
| 158 | + |
| 159 | +- Node.js 20+ |
| 160 | +- Yarn |
| 161 | +- A [Blockfrost](https://blockfrost.io) project ID (free tier works) |
| 162 | +- An [Anthropic API key](https://console.anthropic.com) for AI features (optional) |
| 163 | + |
| 164 | +### 1. Clone and install |
| 165 | + |
| 166 | +```bash |
| 167 | +git clone https://github.com/lidonation/Cardano-mcp |
| 168 | +cd Cardano-mcp |
| 169 | +yarn install |
| 170 | +``` |
| 171 | + |
| 172 | +### 2. Configure environment |
| 173 | + |
| 174 | +```bash |
| 175 | +cp .env.example .env |
| 176 | +``` |
| 177 | + |
| 178 | +Edit `.env`: |
| 179 | + |
| 180 | +```bash |
| 181 | +BLOCKFROST_PROJECT_ID=mainnetXXXXXXXXXXXXXX |
| 182 | +CARDANO_NETWORK=mainnet |
| 183 | +KOIOS_URL=https://api.koios.rest/api/v1 |
| 184 | +ANTHROPIC_API_KEY=sk-ant-... # optional — enables AI sentiment + chatbot |
| 185 | +``` |
| 186 | + |
| 187 | +### 3. Build and register the MCP server |
| 188 | + |
| 189 | +```bash |
| 190 | +yarn build |
| 191 | +``` |
| 192 | + |
| 193 | +**Claude Code** (CLI): |
| 194 | + |
| 195 | +```bash |
| 196 | +claude mcp add cardano -- node /absolute/path/to/Cardano-mcp/dist/index.js |
| 197 | +``` |
| 198 | + |
| 199 | +**Claude Desktop** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`: |
| 200 | + |
| 201 | +```json |
| 202 | +{ |
| 203 | + "mcpServers": { |
| 204 | + "cardano": { |
| 205 | + "command": "node", |
| 206 | + "args": ["/absolute/path/to/Cardano-mcp/dist/index.js"], |
| 207 | + "env": { |
| 208 | + "BLOCKFROST_PROJECT_ID": "mainnetXXXXXXXXXXXXXX", |
| 209 | + "CARDANO_NETWORK": "mainnet" |
| 210 | + } |
| 211 | + } |
| 212 | + } |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +After registration Claude gets 38 new tools: `get_address_utxos`, `list_governance_proposals`, `decode_cbor_datum`, `build_payment_tx`, and 34 more. Ask it "what UTxOs does addr1... hold?" and it calls the tool directly. |
| 217 | + |
| 218 | +### 4. Run the demo app |
| 219 | + |
| 220 | +```bash |
| 221 | +cd demo-app |
| 222 | +yarn install |
| 223 | +yarn dev |
| 224 | +``` |
| 225 | + |
| 226 | +This starts the Express bridge on port 3001 and the Vite frontend on port 5173 concurrently. Open `http://localhost:5173`. |
| 227 | + |
| 228 | +--- |
| 229 | + |
| 230 | +## Lessons learned |
| 231 | + |
| 232 | +**1. Zod defaults don't apply when you bypass the SDK.** |
| 233 | +When calling MCP tool handlers directly (as we do in the Express bridge), the SDK's schema parse layer never runs, so `z.number().default(1)` silently delivers `undefined`. Fix: defensive nullish coalescing everywhere — `page ?? 1`, `count ?? 100`. |
| 234 | + |
| 235 | +**2. SVG in `<img>` is a walled garden.** |
| 236 | +Our animated brand mark uses `<img src="/brand/mark.svg">`. CSS from `globals.css` cannot reach inside it, and `currentColor` resolves to black on a dark background instead of inheriting from the surrounding document. The fix: embed a `<style>` block with `@keyframes` directly inside the SVG file, and replace every `currentColor` with explicit hex values. |
| 237 | + |
| 238 | +**3. Koios is free but occasionally slow.** |
| 239 | +The 8-second timeout on Koios calls was chosen after testing: most responses arrive in under 2 seconds, but tail latencies can spike to 10+ seconds during high load. The Blockfrost fallback covers those gaps without the user ever seeing an error. |
| 240 | + |
| 241 | +**4. IPFS is not always JSON.** |
| 242 | +Some governance metadata anchor URLs point to PDF documents, not CIP-100 JSON-LD. The `fetchIpfs` helper checks `Content-Type` and returns `null` for anything that doesn't parse as JSON, rather than crashing the whole proposal listing. |
| 243 | + |
| 244 | +--- |
| 245 | + |
| 246 | +## What's next |
| 247 | + |
| 248 | +- **Preprod / Preview testnet support** — switch `CARDANO_NETWORK` and all API URLs update automatically; the demo app needs a network selector |
| 249 | +- **Transaction signing** — the `txbuilder` module builds unsigned transactions; a browser wallet integration (Lace, Eternl, Nami via CIP-30) would close the loop |
| 250 | +- **Streaming governance feed** — Kupo's pattern-matching API can watch for new governance actions in real time; wire it to a WebSocket endpoint in the bridge server |
| 251 | + |
| 252 | +--- |
| 253 | + |
| 254 | +## Links |
| 255 | + |
| 256 | +- **Repository**: [github.com/lidonation/Cardano-mcp](https://github.com/lidonation/Cardano-mcp) |
| 257 | +- **Docs**: [cardano-mcp.dev](https://cardano-mcp.dev) |
| 258 | +- **Blockfrost**: [blockfrost.io](https://blockfrost.io) |
| 259 | +- **Koios API**: [api.koios.rest](https://api.koios.rest) |
| 260 | +- **CIP-1694 spec**: [github.com/cardano-foundation/CIPs/tree/master/CIP-1694](https://github.com/cardano-foundation/CIPs/tree/master/CIP-1694) |
| 261 | +- **Model Context Protocol**: [modelcontextprotocol.io](https://modelcontextprotocol.io) |
| 262 | +- **Session Notes**: [Session 18 — Working Group](../docs/working-group/sessions/q2-2026/18-cardano-mcp-server/session-notes/readme.md) |
0 commit comments