Skip to main content
Glama
Intellihackz

quai-mcp-server

by Intellihackz

quai-mcp-server

An MCP (Model Context Protocol) server that exposes Quai Network chain data and read-only interaction tools to AI clients like Claude Desktop and Claude Code. Built with the official @modelcontextprotocol/sdk and quais, Quai's ethers-like SDK.

What is Quai Network, in plain terms

Quai is a proof-of-work, EVM-compatible Layer 1 that scales by sharding: instead of one chain doing all the work, it splits into many chains arranged in a hierarchy.

                Prime chain (1)
               /      |       \
        Region      Region      Region      <- "Cyprus", "Paxos", "Hydra"
       /  |  \      /  |  \     /  |  \
     Zone Zone Zone  ...              9 Zone chains total
  • Prime is the single top-level chain. Every miner mines Prime; it settles state across the whole network but doesn't process user transactions directly.

  • Region chains (currently Cyprus, Paxos, Hydra) sit beneath Prime, aggregating their Zones.

  • Zone chains (Cyprus1/2/3, Paxos1/2/3, Hydra1/2/3 — 9 today, more can be added as the network grows) are where the actual EVM lives: user transactions, contracts, balances, everything.

Unlike sharding designs that split security along with data, Quai keeps security unified across the whole hierarchy while only the data/throughput is split — Prime and Region chains merge-mine with the Zones beneath them.

The part that matters most for tooling: every Quai address is location-aware. The address's own bytes encode which single Zone it lives in (and whether it's on the QUAI ledger, account-based like Ethereum, or the Qi ledger, UTXO-based like Bitcoin). An address on Cyprus1 only exists on Cyprus1 — you can't ask Paxos2 about it. That's why several tools below either resolve the zone for you automatically, or ask you to specify one explicitly.

Tools

Read-only

Tool

What it does

get_balance

QUAI balance for an address. Zone is resolved automatically from the address.

get_block

Block details by number/hash/tag. Requires a shard/zone, since block numbers aren't globally unique across chains.

get_transaction

Transaction + receipt by hash, including which zone it landed on.

resolve_zone

Given an address, report its zone, region, and ledger (Quai vs Qi) — no network call.

call_contract

Read-only eth_call-style contract call (address + ABI fragment + method + args). Zone resolved from the contract address.

search_docs

Search a small curated offline index of Quai docs and get back snippets + links.

get_conversion_rate

Quote a conversion between QUAI and Qi, Quai's own two native ledgers — this is Quai's built-in "swap," not a third-party DEX (none is known to be confirmed on Quai).

None of these can move funds, sign anything, or change on-chain state.

Wallets (custodial: encrypted, named, password-protected)

Tool

What it does

create_wallet

Generates a new QUAI-ledger private key + address, ground to land in a chosen zone (default cyprus1), and stores it encrypted under a name and password. By default (pairQiWallet: true) also creates a matching Qi wallet under the same name/password/zone, so a QUAI→Qi conversion always has somewhere real to land — set pairQiWallet: false for a QUAI-only wallet.

import_wallet

Same encrypted storage, for a QUAI-ledger private key you already have.

create_qi_wallet

Generates a new Qi-ledger (UTXO-based) wallet — an HD wallet with a mnemonic, since Qi needs address derivation and UTXO scanning, not a single keypair. Encrypted the same way.

import_qi_wallet

Same encrypted storage, for a Qi mnemonic phrase you already have.

list_wallets

Lists stored wallets of both kinds (name, ledger, address, zone). No password needed — only spending or checking a Qi balance needs it.

send_transaction

Signs and sends QUAI from a stored QUAI wallet. Two-step confirmation (see below). Sender/recipient can be in different zones — that's an external transaction (ETX), handled automatically by the network. If the recipient is a Qi address, this doubles as the QUAI→Qi conversion path (see below).

get_qi_balance

Total and spendable Qi balance for a Qi wallet. Needs the password — see "Why Qi needs the password" below.

convert_qi_to_quai

Converts Qi held in a Qi wallet into QUAI, sent to a QUAI address. Two-step confirmation, same pattern as send_transaction.

get_qi_payment_code

Gets a Qi wallet's reusable BIP-47 payment code — what you hand someone so they can send_qi to you. Needs the password (purely local, no network call).

send_qi

Sends Qi from a Qi wallet to a recipient's payment code (not a plain address) — see "Qi → Qi sending" below. Two-step confirmation, same pattern as the other write tools.

This server holds keys on your behalf once you create or import a wallet — it's custodial in that narrow, local sense, the same way a geth keystore or MetaMask's local vault is. It does not operate as a hosted service for other people's funds; everything lives in a directory on the machine running the server, encrypted with a password only you know.

How the encryption works: each wallet is a private key in the standard Web3 Secret Storage (V3 keystore) format — the same format geth and MetaMask use — via quais' encryptKeystoreJson. Concretely: the password is stretched with scrypt (N=2^17, r=8, p=1, the standard "expensive" cost parameters — this deliberately makes each password guess slow), the private key is encrypted with AES-128-CTR, and a MAC over the ciphertext detects a wrong password (or a tampered file) before any key material is derived from it. This is a well-reviewed, widely-deployed scheme; nothing here is custom cryptography.

Where wallets live: ~/.quai-mcp-server/wallets/ by default (override with QUAI_WALLET_DIR) — QUAI wallets as <name>.json, Qi wallets as <name>.qi.json. The directory is created 0700 and each keystore file 0600 (owner-read/write only, best-effort on non-POSIX platforms) — enforced explicitly after creation, not just left to the process umask. The address is stored in the clear in both cases (it's public information; that's how list_wallets and QUAI-side previews work without a password), but the private key (or mnemonic, for Qi) is never written, logged, or returned in plaintext by any tool.

Naming: a name identifies at most one QUAI wallet and at most one Qi wallet — they're independent keystores (different files, different secrets, entirely unrelated key material) that happen to share a label. You can't create two QUAI wallets (or two Qi wallets) with the same name, but reusing a QUAI wallet's name for a Qi wallet is exactly how create_wallet's pairing works, and create_qi_wallet/import_qi_wallet allow it deliberately for the same reason.

Qi wallets are HD wallets under the hood, but this server only ever stores the mnemonic — never the derived address tree or any UTXO/scan state. create_qi_wallet/import_qi_wallet encrypt {address, privateKey, mnemonic} via the exact same encryptKeystoreJson call as the QUAI side (the address/privateKey fields there are just the wallet's first derived address, present so the file is a normal, valid V3 keystore); the meaningful secret is the mnemonic. Every later operation (get_qi_balance, convert_qi_to_quai) reconstructs a fresh QiHDWallet from that mnemonic and re-derives the same receiving address on demand -- deterministically, since HD derivation for a fixed account/zone always produces the same address. This was verified directly: exporting a wallet's mnemonic and re-importing it under a different name reproduced the identical address. The tradeoff is that every Qi operation re-derives from scratch rather than reading a cache, which is simpler to reason about and can't drift from what the mnemonic actually implies, at the cost of needing the password more often than the QUAI side does (see below).

Why Qi needs the password more often: QUAI's get_balance reads a public account balance straight from the chain -- no secret needed. Qi has no such thing: a "balance" is the sum of unspent transaction outputs (UTXOs) that belong to addresses only the wallet's mnemonic can derive, so computing it at all means reconstructing the wallet first. That's why get_qi_balance takes a password (QUAI's get_balance doesn't), and why convert_qi_to_quai's preview step can quote a conversion rate but can't confirm you actually have enough Qi to spend -- that check only happens once the password arrives at the confirm step.

Password rules: minimum 8 characters, checked before anything is encrypted. There's no separate rate-limiting on wrong-password attempts — scrypt's cost parameters already make each guess computationally expensive, which is the standard defense for this kind of local keystore.

Confirmation flow for send_transaction, convert_qi_to_quai, and send_qi: all three always require two calls, and only the second one needs the password.

  1. Call with the destination and amount (walletName/to/amount for send_transaction; walletName/recipientPaymentCode/amount/destinationZone for send_qi; the to-shaped version for convert_qi_to_quai) — no password required yet. Nothing is broadcast. You get back a preview — resolved zones, an estimate where one exists (gas for a send, converted amount for a conversion; send_qi has none, since it's a 1:1 transfer), and a confirmationToken valid for 2 minutes.

  2. Call again with the same parameters, plus confirm: true, that confirmationToken, and the wallet's password. Only then is the key/mnemonic decrypted and the transaction actually signed and sent.

A token is single-use and tied to the exact parameters previewed — if anything changes, the token expired, or it was already used, step 2 fails with a clear error and you preview again. This works the same way regardless of whether the MCP client itself has a tool-approval UI, so it's a real gate rather than relying on the client to provide one. A wrong password fails cleanly (Incorrect password for wallet "...") without leaking whether the token/params were otherwise valid.

There is intentionally no export_wallet/"show private key or mnemonic" tool — once a secret is in the store, the only way out through this server is to sign with it.

QUAI ↔ Qi conversion ("swap"): Quai has a native, protocol-level conversion between its two ledgers — QUAI (account-based) and Qi (UTXO-based, like Bitcoin) — with an on-chain exchange rate, not a third-party DEX. get_conversion_rate quotes either direction, with no wallet needed. Both execution directions are now implemented:

  • QUAI → Qi: just a normal send_transaction to a Qi-ledger address (e.g. one from create_qi_wallet). The tool detects this automatically (isConversion: true in the preview) and shows the estimated Qi received alongside the usual gas/balance info.

  • Qi → QUAI: convert_qi_to_quai, using quais' QiHDWallet.convertToQuai under the hood, following the same preview/confirm/password pattern as send_transaction.

Qi → Qi sending: Qi wallets don't send to each other's addresses directly. Instead, each Qi wallet has a reusable BIP-47 payment code (get_qi_payment_code) — share that the way you'd share an address, but a fresh one-time address is derived from it for every payment, for privacy. To send, the sender "opens a channel" with the recipient's payment code (send_qi does this automatically) — this is pure local ECDH between the two payment codes, deterministic and reproducible, no on-chain action or persisted state involved. The catch is on the receiving end: those pairwise-derived addresses aren't part of the wallet's normal deterministic address sequence, so nothing will find funds sent that way unless you tell it to look. Concretely: after someone pays your Qi wallet via payment code, pass their payment code into get_qi_balance's counterpartyPaymentCodes — it opens that same channel and includes it in the balance. There's no notification mechanism (on-chain or otherwise) that tells the receiver a payment-code payment arrived; the two sides have to already know about each other out of band, the same way you'd need to know an address before checking its balance. send_qi also supports cross-zone sends (a destinationZone separate from the sender's own zone), the same way send_transaction's ETX and QiHDWallet's own zone model do.

One known rough edge: send_qi's preview step doesn't validate the payment code's format up front (there's no exported validator to check it against), so a malformed code will preview fine and only fail once you confirm — safely (nothing is sent, no funds are at risk), just later than ideal.

Not yet implemented: deploy_contract, request_faucet.

Honesty about what's tested here, updated: the full send_qi / payment-code loop was verified live against mainnet with two real wallets — a real, correctly-formatted BIP-47 payment code (PM8T...) was generated and confirmed deterministic across calls, a preview correctly detected cross-zone vs. same-zone, a confirm against an empty wallet failed with a genuine SDK error (No Qi available in zone) rather than crashing, and get_qi_balance correctly isolated an invalid counterparty payment code into rejectedPaymentCodes without failing the whole call. What's still unverified, for the same reason as everywhere else in this doc: an actual payment-code send completing between two funded wallets, since that needs real Qi and wasn't done without being asked.

Honesty about what's tested here: everything above was exercised against live mainnet, including a determinism check (exporting a Qi wallet's mnemonic and reimporting it under a different name reproduced the identical address) and real error paths (wrong password, insufficient QUAI gas, and a real QiHDWallet error -- No Qi available in zone -- when attempting to convert from an empty Qi wallet). What has not been exercised is a convert_qi_to_quai or QUAI→Qi conversion actually completing against a wallet that holds real funds, since that requires spending real money and wasn't done without being asked to.

Install

npm install
npm run build

Or run directly without installing, once published:

npx quai-mcp-server

Requirements

  • Node.js 18+

Configuration (environment variables)

All optional — sensible defaults point at Quai mainnet.

Variable

Default

Purpose

QUAI_MAINNET_RPC_URL

https://rpc.quai.network

Mainnet RPC gateway used by tools when network: "mainnet" (the default).

QUAI_TESTNET_RPC_URL

https://orchard.rpc.quai.network

Orchard testnet RPC gateway used when network: "testnet".

QUAI_WALLET_DIR

~/.quai-mcp-server/wallets

Where encrypted wallet keystore files are stored.

Every tool also accepts a network argument ("mainnet" or "testnet") per call, so a client can query either network without restarting the server.

On keys: see "Wallets" above. Keys only exist as plaintext in memory for the duration of a create_wallet/import_wallet/send_transaction call that needs them — never on disk, never logged. Treat QUAI_WALLET_DIR (and whatever machine runs this server) as you would any other local secret store: anyone with filesystem access to that directory and enough compute to brute-force a weak password can eventually decrypt a wallet, same as a local geth keystore or MetaMask vault.

Register with Claude Desktop

Add this to your Claude Desktop MCP config (claude_desktop_config.json — on macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "quai": {
      "command": "npx",
      "args": ["quai-mcp-server"]
    }
  }
}

Or, if you've cloned and built this repo locally instead of using a published package:

{
  "mcpServers": {
    "quai": {
      "command": "node",
      "args": ["/absolute/path/to/quai-mcp-server/dist/index.js"]
    }
  }
}

To point it at testnet by default, add an env block:

{
  "mcpServers": {
    "quai": {
      "command": "npx",
      "args": ["quai-mcp-server"],
      "env": {
        "QUAI_TESTNET_RPC_URL": "https://orchard.rpc.quai.network"
      }
    }
  }
}

(then pass "network": "testnet" in individual tool calls — the env vars set the endpoint, not the default network per call).

Register with Claude Code

claude mcp add quai -- npx quai-mcp-server

or, for a local build:

claude mcp add quai -- node /absolute/path/to/quai-mcp-server/dist/index.js

Development

npm run dev     # tsc --watch
npm run build   # one-shot build to dist/
npm start        # run the built server directly (stdio) -- mainly useful for manual smoke tests

The server speaks MCP over stdio only in v1; there is no HTTP transport.

Design notes

  • quais over raw RPC: every tool goes through the quais SDK's JsonRpcProvider, Contract, and address utilities rather than hand-rolled eth_/quai_ JSON-RPC calls, so zone resolution, response formatting, and error shapes stay consistent with the rest of the Quai ecosystem.

  • One provider, many zones: a single JsonRpcProvider pointed at a base gateway URL (e.g. https://rpc.quai.network) auto-discovers active zones from the Prime chain and routes each call to the right one — most tools never construct a per-zone URL.

  • Custody, done with standard tools, not custom crypto: wallets are stored using quais' implementation of the Ethereum V3 keystore format (scrypt + AES-128-CTR + MAC) — the same well-reviewed scheme geth and MetaMask use — rather than anything hand-rolled. See "Wallets" above for the full model.

  • Errors are text, not stack traces: RPC/contract errors are caught and rewritten into short, specific messages (e.g. "Contract call reverted: ...", "Insufficient funds: ...", "Incorrect password for wallet...", "not a validly checksummed Quai address") instead of leaking raw exception objects to the model.

  • Confirmation is a real gate, not just a client hint: write tools are annotated readOnlyHint: false (and destructiveHint: true for sending) so MCP clients with their own approval UI show one, but send_transaction additionally enforces its own preview → token → password handshake server-side (src/confirmations.ts for the token, src/walletStore.ts + decryptKeystoreJson for the password), so it's still safe to call from a client with no approval UI at all.

  • Password only needed once, at the last moment: previewing a send resolves the wallet's address straight from the unencrypted part of its keystore file and uses a VoidSigner (a quais signer that can estimate gas but not sign) to estimate cost — no decryption, no password. Only the final confirm: true call decrypts the key, and only for the duration of that one call.

  • ETX is not a separate code path: sending to an address in a different zone uses the exact same send_transaction call as a same-zone send — Quai's network handles the cross-zone routing (as an external transaction) transparently once the signed transaction reaches the sender's zone. The tool just detects and reports the zones involved so the caller knows what to expect.

  • Qi wallets are stateless between calls, on purpose: create_qi_wallet/import_qi_wallet only ever encrypt a mnemonic. get_qi_balance and convert_qi_to_quai reconstruct the QiHDWallet from scratch each call and re-derive its address (src/qiWallet.ts) rather than reading any cached address/UTXO state -- there isn't any to read. This traded a little performance (every Qi operation re-derives and re-queries rather than hitting a cache) for a simpler, harder-to-get-wrong security story: the only thing ever at rest is the one secret that matters.

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Provide AI agents and automation tools with contextual access to blockchain data including balance…

  • Read-only on-chain intelligence for AI agents on Base: balances, tokens, gas, tx status.

  • Read-only on-chain intel for AI agents on Base: balances, tokens, gas, tx status. No API keys.

View all MCP Connectors

Latest Blog Posts

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/Intellihackz/quai-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server