monad-mcp
Allows staking and unstaking MON tokens via FastLane's liquid staking vault, including querying positions.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@monad-mcpcheck my MON balance"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
monad-mcp
MCP server for the Monad blockchain. Your agent's gateway to balances, transfers, swaps, and DeFi on Monad — with signing handled by a Privy embedded wallet, never by the agent.
Built around a simple shape — stored requests + approval URLs + skill plugins — on Monad mainnet 143 / testnet 10143, with Privy as the wallet provider.
🌐 Landing page + live read-only demo: pareen.github.io/monad-mcp — query real Monad balances in your browser, exactly what the agent sees.
🔌 Connect it — no install, no terminal. In Claude Desktop or ChatGPT, add a custom connector and paste this URL:
https://monad-mcp.fly.dev/mcpThat's the whole setup — read tools (balances, prices, portfolios, token checks) work immediately. Step-by-step for each app: docs/connect-claude.md.
Prefer the terminal? One line in Claude Code:
claude mcp add --transport http monad https://monad-mcp.fly.dev/mcp # hosted
claude mcp add monad -- npx -y monad-mcp # or run it locallyThe hosted endpoint runs in split mode: read tools are public (paste the URL,
no login — safe to share) and write tools are gated behind a Privy sign-in,
returning auth_required until you authenticate. The hosted sign-in path is wired
but still being verified end-to-end, so the tested route for transactions is your
own Privy-signed instance — see docs/connect-claude.md, docs/FAQ.md for common security/setup questions, and docs/DEPLOY.md.
🧭 Monad ≠ Ethereum: the server ships agent-facing guidance on Monad's quirks (128 KB contracts, gas_limit billing, speculative latest, eth_getLogs caps, FastLane/MEV) as monad://guide/* MCP resources — see docs/building-on-monad.md.
What's in the box
Core tools (work on any address; auth required only where noted):
Tool | Auth | What it does |
| none | Provision a Privy user + Monad-ready embedded wallet. Self-serve onboarding. |
| required | User id, wallet address, and active session-key grants. |
| required | Returns the connected wallet's address. |
| optional | Native MON balance. Accepts a 0x address or a |
| optional | ERC-20 balance with decimals + symbol. Accepts a |
| optional | All canonical token balances × USD prices via DexScreener. Accepts a |
| optional | Recent ERC-20 transfers via RPC log scan + explorer URL. Accepts a |
| none | Receipt for a tx hash. |
| optional | Dry-run a call (eth_call), returns return data or revert reason. |
| none | Risk heuristics: canonical-list membership, bytecode, DEX liquidity, pair age. |
| none | Symbol/name/alias → token address (canonical list + DexScreener fallback). |
| none | USD price from the deepest-liquidity DexScreener pair on Monad. |
| none | Built-in list of well-known Monad tokens. |
| none | Nad Name Service (nad.domains): |
| none | Call any view/pure function on any Monad contract (ABI provided inline). |
| none | Decode hex return data against an ABI. |
| optional | LiFi aggregator quote from any chain into Monad. |
| none | Status of a pending approval request. |
| required | Native MON or ERC-20 transfer. Recipient may be a 0x address or a |
| required | Send a tx to any contract (any non-view function). |
| required | x402 (HTTP 402) — signs an EIP-3009 USDC authorization via Privy and retries with X-PAYMENT. |
| required | Submit the source-chain tx returned by |
| required | Authorize the agent to spend up to N MON without per-tx approvals. |
| required | Show active + recent grants. |
| required | Cancel a grant immediately. |
Skill plugins (additive, drop in your own under src/plugins/):
Uniswap (
uniswap_quote,uniswap_swap,approve_erc20) — exact-input single-hop swaps on Uniswap v3 (testnet + mainnet).Kintsu (
kintsu_stake,kintsu_request_unstake,kintsu_claim_unstake,kintsu_position) — liquid staking MON → sMON via Kintsu's ERC-7535 vault. Two-step unstake with batch processing.FastLane (
fastlane_stake,fastlane_unstake,fastlane_position) — liquid staking MON → shMON via FastLane's shMONAD ERC-4626 vault. Payable native deposit, synchronous redeem (no epoch wait); shMON keeps earning staking + MEV rewards.Morpho (
morpho_supply,morpho_withdraw,morpho_borrow,morpho_repay,morpho_position,morpho_market) — lending on Morpho Blue's singleton. Caller passes MarketParams inline; plugin derives the market id.Kuru (
kuru_best_bid_ask,kuru_market_params,kuru_place_limit,kuru_cancel_orders,kuru_market_swap) — fully-on-chain CLOB. Ships with known mainnet markets (MON/USDC, MON/AUSD, WETH/USDC); accepts raw addresses for new ones.
All write tools across plugins auto-route through a covering session-key grant (see "Session keys" below); otherwise they return an approval URL.
Mainnet-only plugins (Kintsu / FastLane / Morpho / Kuru) are skipped at boot when MONAD_DEFAULT_NETWORK=testnet. Override per-tool with network: "mainnet".
Related MCP server: Monad MCP
Quickstart
1. Install + configure
git clone https://github.com/you/monad-mcp.git
cd monad-mcp
npm install
cp .env.example .envEdit .env. Minimum config to do anything useful with write tools:
PRIVY_APP_ID=clxxxxxxxxxxx # from https://dashboard.privy.io
PRIVY_APP_SECRET=... # from the same place
PRIVY_VERIFICATION_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----" # optional but recommended
# Where this server is reachable from a browser (used in approval URLs).
PUBLIC_BASE_URL=http://localhost:8787
PORT=8787
# Which Monad network to default to when a tool call omits `network`.
MONAD_DEFAULT_NETWORK=testnetFor the Uniswap plugin, supply contract addresses (verify against the canonical Uniswap deployment for Monad before using):
UNISWAP_MAINNET_QUOTER_V2=0x...
UNISWAP_MAINNET_SWAP_ROUTER_02=0x...
UNISWAP_TESTNET_QUOTER_V2=0x...
UNISWAP_TESTNET_SWAP_ROUTER_02=0x...2. Privy app setup
In the Privy dashboard:
Create an app. Enable embedded wallets under "Wallets".
Under "Networks", add Monad mainnet (chainId
143) and/or testnet (chainId10143) as an EVM chain.Copy the App ID + App Secret into
.env.Copy the JWT verification key (PEM-encoded ES256 public key) into
PRIVY_VERIFICATION_KEYfor fastest token verification. If omitted, the server falls back to JWKS over the network.(Optional) Configure wallet policies (spend limits, allowlists) to gate what the agent can do.
2a. Bootstrap server-side wallet authorization (one-time)
Privy embedded wallets are user-owned by default — the server cannot sign for them. The MCP server needs a P-256 key quorum registered with Privy, attached as an additional_signer on every wallet it provisions. Bootstrap it:
npm run bootstrap:auth-key
# Generates a P-256 keypair, registers it as a Privy key quorum, prints two env vars.
# Paste PRIVY_AUTHORIZATION_PRIVATE_KEY and PRIVY_KEY_QUORUM_ID into .env.After bootstrap, every create_user call attaches the quorum at wallet creation, and every transfer/swap includes the private key in authorization_context. Important: wallets minted before you set these env vars are stranded — Privy doesn't let you retrofit an additional_signer (updating authorization requires existing authorization). Always bootstrap first.
3. Run
# Stdio mode for Claude Desktop, Cursor, etc.
npm run build
npm start
# HTTP / Streamable HTTP mode for remote MCP clients (Claude Web, ChatGPT).
npm run start:http
# Server listens on http://localhost:8787/mcp4. Wire it into Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json. The --env-file flag loads PRIVY_*, MONAD_*, and PUBLIC_BASE_URL from your .env into the spawned process:
{
"mcpServers": {
"monad": {
"command": "node",
"args": [
"--env-file=/absolute/path/to/monad-mcp/.env",
"/absolute/path/to/monad-mcp/dist/index.js"
]
}
}
}You'll also need the HTTP server running (npm run start:http) for the approval flow — the stdio MCP returns approval URLs that point at the HTTP server. See docs/claude-desktop.md for the full dogfood walkthrough (bootstrap, funding, first transfer).
Public reads, no self-hosting: to use the read tools (balances, portfolio, history) without building anything, point Claude Desktop at the hosted endpoint via the
mcp-remoteshim —"args": ["-y", "mcp-remote", "https://monad-mcp.fly.dev/mcp"]. Writes (transfers/swaps) are gated behind a Privy sign-in; the verified path for them is a self-hosted signer.
5. Try a prompt
What's the balance of 0x000…0000 on Monad testnet?
Send 0.01 MON to 0xabc… on Monad testnet.For the second prompt the agent calls transfer, hands you a link like http://localhost:8787/approve/<uuid>, and you approve in your browser. The agent polls poll_request and reports the tx hash.
Architecture
agent (Claude / ChatGPT / Cursor)
│
│ MCP over stdio OR Streamable HTTP (with OAuth 2.1 bearer)
▼
monad-mcp server
│
├── core tools viem → Monad RPC (mainnet 143 / testnet 10143)
├── skill plugins Uniswap / Kuru / Kintsu / Morpho (additive)
│
├── auth bridge Privy server SDK (verify access token, resolve embedded wallet)
│
└── stored requests pending tx ↔ approval URL ↔ user signature
▲
│
└── /approve/:id → Privy hosted signing → tx hashWrite tools follow the stored request pattern:
Tool builds an unsigned
{ to, value, data }payload and a human-readable summary.Server stores it under a UUID and returns
https://your-server/approve/<uuid>to the agent.User opens the URL — page shows the summary + asset diff + signs via their Privy embedded wallet.
Server records the tx hash; agent polls
poll_requestand continues.
The agent never touches a private key. Privy's TEE-backed key custody + wallet policies are the security envelope.
Adding your own skill plugin
Plugins live in src/plugins/<id>/ and export a SkillPlugin:
import type { SkillPlugin } from "../types.js";
import { registerTool } from "../../tools/registry.js";
export const myPlugin: SkillPlugin = {
id: "myproto",
name: "MyProto on Monad",
description: "What it does in one sentence.",
networks: ["mainnet", "testnet"],
register(mcp, server) {
registerTool(mcp, server, myReadTool);
registerTool(mcp, server, myWriteTool);
},
};Register it in src/plugins/index.ts. Write tools just need to construct a { to, value, data } payload and call server.store.create(...) — the approval flow is shared.
Testing
npm test # vitest unit tests (no network) — 56 tests
npm run test:integration # integration tests against live testnet RPC + Privy
npm run typecheck
npm run lint56 unit tests cover the chain config, both stores (requests + grants), tool registry (auth gating, schema validation), every core tool, every session-key surface, and the Uniswap plugin's quote sweep.
The integration suite runs transfer end-to-end on Monad testnet through the Privy server SDK: build → sign → broadcast → poll receipt. Gated on PRIVY_APP_ID, PRIVY_APP_SECRET, PRIVY_AUTHORIZATION_PRIVATE_KEY, PRIVY_KEY_QUORUM_ID, and MONAD_MCP_E2E_USER_ID being set + the wallet being funded; cleanly skips otherwise so it doesn't break CI without secrets.
Security model
Keys: Privy holds the key in a TEE. The MCP server never sees a private key.
Authorization to act: every write tool builds a stored request; nothing happens until the user clicks Approve on the approval page.
Token scope: the OAuth 2.1 bearer in the MCP transport identifies which user; it does not authorize which tx — that's gated by the explicit approval click + Privy wallet policies.
Stale link defense: stored requests expire after
ttl_seconds(default 5 min); expired requests can't be approved.CSRF on /reject: rejection is intentionally permissionless — worst case, a stale link can't be re-used. Approval requires a Bearer token tied to the requesting user.
For production, the stored-request and grant stores already support a Postgres backend (set STORE_BACKEND=postgres); still on the hardening list are rate limiting on /approve and /submit, and per-tool spend caps via Privy policies.
Session keys: skipping per-tx approvals
By default every write tool returns an approval URL the user has to click. That's fine for one-shot prompts, fatal for anything agentic ("DCA $100 into MON every Friday", "rebalance my LP weekly"). The fix is grant_session_key:
> Grant the agent up to 0.5 MON for the next 24h to send to 0xabc…
agent calls grant_session_key({
spend_cap_mon: "0.5",
ttl_seconds: 86400,
allowed_targets: ["0xabc..."],
})
→ returns an approval URL. User clicks once.
> Now send 0.01 MON to 0xabc…
agent calls transfer({ to: "0xabc...", amount: "0.01" })
→ no approval URL. Tool returns the tx hash immediately,
grant remaining: 0.49 MON.Grants are scoped by:
spend_cap_mon— total native MON the agent can spend (running total tracked across calls)ttl_seconds— grant expiry window (max 30 days)allowed_targets— optional recipient/contract allowlist (empty = any)allowed_selectors— optional 4-byte function selector allowlist (e.g.0xa9059cbb= ERC-20 transfer only)
Coverage is checked against every write call; if the grant doesn't cover (wrong target, wrong selector, would exceed cap), the tool falls back to the approval-URL flow. Revoke any time with revoke_session_key.
Limits: v1 tracks native-MON spend cap only. ERC-20 token amounts aren't deducted from the cap — instead, restrict ERC-20 access by listing the token contract in allowed_targets. Per-token spend caps are a v2 add.
End-to-end testing on Monad testnet
# 1. Provision a test user + embedded wallet (one-time, costs a Privy seat).
npm run e2e:create-user
# Prints: user_id, wallet_address, faucet URL. Save user_id to .env.
# 2. Fund the printed wallet at https://testnet.monad.xyz/
# 3. Run the round-trip transfer test.
npm run e2e:transfer
# Builds a transfer through runTool, signs+broadcasts via Privy, waits for receipt.
# 4. Sanity-check Privy creds reach the API.
npm run verify:privyThe e2e script reuses MONAD_MCP_E2E_USER_ID from env so you don't burn a new Privy seat on each run.
What's next
Browser-first signing path (Privy web SDK in the approval page → no server-side
sendTransactionround-trip).More plugins — Neverland lending (Aave V3 fork on Monad), additional DEXes/perps.
Block-explorer integration once Monad's explorer API stabilizes (
get_transaction_historywill return full decoded history).x402 payment support (pay for x402-enabled services).
Per-token (ERC-20) spend caps on session grants — today's cap is a single native-MON budget per grant.
Universal contract reader/writer for "talk to any Monad contract" without writing a per-protocol plugin.
License
MIT — see LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceA Model Context Protocol server that enables AI models to interact with the Monad testnet for checking token balances, sending transactions, and deploying smart contracts.Last updated7Apache 2.0
- -license-quality-maintenanceAn MCP server that allows Claude Desktop to query Monad testnet for MON token balances of accounts.Last updated
- AlicenseAqualityDmaintenanceMCP server with 43 tools for blockchain data — token lookups, wallet balances, live chain queries across 10+ networks, and full API documentation search.Last updated27215Apache 2.0
Related MCP Connectors
Solana MCP for wallets, trades, markets, PnL, transfers, onchain data, signable swaps and API tools.
MCP server for Klever blockchain smart contract development.
MCP server for Blockscout
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/pareen/monad-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server