{
  "markdown": "# monad-mcp\n\nMCP server for the [Monad](https://monad.xyz) blockchain. Your agent's gateway to balances, transfers, swaps, and DeFi on Monad — with signing handled by a [Privy](https://privy.io) embedded wallet, never by the agent.\n\nBuilt around a simple shape — stored requests + approval URLs + skill plugins — on Monad mainnet `143` / testnet `10143`, with [Privy](https://privy.io) as the wallet provider.\n\n**🌐 Landing page + live read-only demo:** [pareen.github.io/monad-mcp](https://pareen.github.io/monad-mcp) — query real Monad balances in your browser, exactly what the agent sees.\n\n**🔌 Connect it — no install, no terminal.** In **Claude Desktop** or **ChatGPT**,\nadd a custom connector and paste this URL:\n\n```\nhttps://monad-mcp.fly.dev/mcp\n```\n\nThat's the whole setup — read tools (balances, prices, portfolios, token checks)\nwork immediately. Step-by-step for each app: [docs/connect-claude.md](docs/connect-claude.md).\n\nPrefer the terminal? One line in [Claude Code](https://claude.com/claude-code):\n\n```bash\nclaude mcp add --transport http monad https://monad-mcp.fly.dev/mcp   # hosted\nclaude mcp add monad -- npx -y monad-mcp                              # or run it locally\n```\n\nThe hosted endpoint runs in **split** mode: **read tools are public** (paste the URL,\nno login — safe to share) and **write tools are gated** behind a Privy sign-in,\nreturning `auth_required` until you authenticate. The hosted sign-in path is wired\nbut still being verified end-to-end, so the tested route for transactions is your\nown Privy-signed instance — see [docs/connect-claude.md](docs/connect-claude.md#sending-transactions-writes), [docs/FAQ.md](docs/FAQ.md) for common security/setup questions, and [docs/DEPLOY.md](docs/DEPLOY.md).\n\n**🧭 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](docs/building-on-monad.md).\n\n## What's in the box\n\n**Core tools** (work on any address; auth required only where noted):\n\n| Tool | Auth | What it does |\n|------|------|--------------|\n| `create_user` | none | Provision a Privy user + Monad-ready embedded wallet. Self-serve onboarding. |\n| `whoami` | required | User id, wallet address, and active session-key grants. |\n| `get_address` | required | Returns the connected wallet's address. |\n| `get_balance` | optional | Native MON balance. Accepts a 0x address or a `.nad` name. |\n| `get_token_balance` | optional | ERC-20 balance with decimals + symbol. Accepts a `.nad` name. |\n| `get_portfolio` | optional | All canonical token balances × USD prices via DexScreener. Accepts a `.nad` name. |\n| `get_transaction_history` | optional | Recent ERC-20 transfers via RPC log scan + explorer URL. Accepts a `.nad` name. |\n| `get_tx_receipt` | none | Receipt for a tx hash. |\n| `simulate_transaction` | optional | Dry-run a call (eth_call), returns return data or revert reason. |\n| `check_token` | none | Risk heuristics: canonical-list membership, bytecode, DEX liquidity, pair age. |\n| `resolve_token` | none | Symbol/name/alias → token address (canonical list + DexScreener fallback). |\n| `get_token_price` | none | USD price from the deepest-liquidity DexScreener pair on Monad. |\n| `list_canonical_tokens` | none | Built-in list of well-known Monad tokens. |\n| `resolve_name` | none | Nad Name Service (nad.domains): `.nad` name → address, and address → primary `.nad` name. |\n| `read_contract` | none | Call any view/pure function on any Monad contract (ABI provided inline). |\n| `decode_return_data` | none | Decode hex return data against an ABI. |\n| `bridge_quote` | optional | LiFi aggregator quote from any chain into Monad. |\n| `poll_request` | none | Status of a pending approval request. |\n| `transfer` | required | Native MON or ERC-20 transfer. Recipient may be a 0x address or a `.nad` name. Auto-executes under a session grant, else → approval URL. |\n| `write_contract` | required | Send a tx to any contract (any non-view function). |\n| `pay_for_service` | required | x402 (HTTP 402) — signs an EIP-3009 USDC authorization via Privy and retries with X-PAYMENT. |\n| `bridge_execute` | required | Submit the source-chain tx returned by `bridge_quote` (Monad sources only). |\n| `grant_session_key` | required | Authorize the agent to spend up to N MON without per-tx approvals. |\n| `list_session_keys` | required | Show active + recent grants. |\n| `revoke_session_key` | required | Cancel a grant immediately. |\n\n**Skill plugins** (additive, drop in your own under `src/plugins/`):\n\n- **Uniswap** (`uniswap_quote`, `uniswap_swap`, `approve_erc20`) — exact-input single-hop swaps on Uniswap v3 (testnet + mainnet).\n- **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.\n- **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.\n- **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.\n- **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.\n\nAll write tools across plugins auto-route through a covering session-key grant (see \"Session keys\" below); otherwise they return an approval URL.\n\nMainnet-only plugins (Kintsu / FastLane / Morpho / Kuru) are skipped at boot when `MONAD_DEFAULT_NETWORK=testnet`. Override per-tool with `network: \"mainnet\"`.\n\n## Quickstart\n\n### 1. Install + configure\n\n```bash\ngit clone https://github.com/you/monad-mcp.git\ncd monad-mcp\nnpm install\ncp .env.example .env\n```\n\nEdit `.env`. Minimum config to do anything useful with write tools:\n\n```bash\nPRIVY_APP_ID=clxxxxxxxxxxx          # from https://dashboard.privy.io\nPRIVY_APP_SECRET=...                # from the same place\nPRIVY_VERIFICATION_KEY=\"-----BEGIN PUBLIC KEY-----\\n...\\n-----END PUBLIC KEY-----\"  # optional but recommended\n\n# Where this server is reachable from a browser (used in approval URLs).\nPUBLIC_BASE_URL=http://localhost:8787\nPORT=8787\n\n# Which Monad network to default to when a tool call omits `network`.\nMONAD_DEFAULT_NETWORK=testnet\n```\n\nFor the Uniswap plugin, supply contract addresses (verify against the canonical Uniswap deployment for Monad before using):\n\n```bash\nUNISWAP_MAINNET_QUOTER_V2=0x...\nUNISWAP_MAINNET_SWAP_ROUTER_02=0x...\nUNISWAP_TESTNET_QUOTER_V2=0x...\nUNISWAP_TESTNET_SWAP_ROUTER_02=0x...\n```\n\n### 2. Privy app setup\n\nIn the Privy dashboard:\n\n1. Create an app. Enable **embedded wallets** under \"Wallets\".\n2. Under \"Networks\", add Monad mainnet (chainId `143`) and/or testnet (chainId `10143`) as an EVM chain.\n3. Copy the App ID + App Secret into `.env`.\n4. Copy the JWT verification key (PEM-encoded ES256 public key) into `PRIVY_VERIFICATION_KEY` for fastest token verification. If omitted, the server falls back to JWKS over the network.\n5. (Optional) Configure wallet policies (spend limits, allowlists) to gate what the agent can do.\n\n### 2a. Bootstrap server-side wallet authorization (one-time)\n\nPrivy 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:\n\n```bash\nnpm run bootstrap:auth-key\n# Generates a P-256 keypair, registers it as a Privy key quorum, prints two env vars.\n# Paste PRIVY_AUTHORIZATION_PRIVATE_KEY and PRIVY_KEY_QUORUM_ID into .env.\n```\n\nAfter 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.\n\n### 3. Run\n\n```bash\n# Stdio mode for Claude Desktop, Cursor, etc.\nnpm run build\nnpm start\n\n# HTTP / Streamable HTTP mode for remote MCP clients (Claude Web, ChatGPT).\nnpm run start:http\n# Server listens on http://localhost:8787/mcp\n```\n\n### 4. Wire it into Claude Desktop\n\nAdd 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:\n\n```json\n{\n  \"mcpServers\": {\n    \"monad\": {\n      \"command\": \"node\",\n      \"args\": [\n        \"--env-file=/absolute/path/to/monad-mcp/.env\",\n        \"/absolute/path/to/monad-mcp/dist/index.js\"\n      ]\n    }\n  }\n}\n```\n\nYou'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](docs/claude-desktop.md) for the full dogfood walkthrough (bootstrap, funding, first transfer).\n\n> **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-remote`](https://www.npmjs.com/package/mcp-remote) shim — `\"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.\n\n### 5. Try a prompt\n\n```\nWhat's the balance of 0x000…0000 on Monad testnet?\nSend 0.01 MON to 0xabc… on Monad testnet.\n```\n\nFor 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.\n\n## Architecture\n\n```\nagent (Claude / ChatGPT / Cursor)\n   │\n   │  MCP over stdio OR Streamable HTTP (with OAuth 2.1 bearer)\n   ▼\nmonad-mcp server\n   │\n   ├── core tools           viem → Monad RPC (mainnet 143 / testnet 10143)\n   ├── skill plugins        Uniswap / Kuru / Kintsu / Morpho (additive)\n   │\n   ├── auth bridge          Privy server SDK (verify access token, resolve embedded wallet)\n   │\n   └── stored requests      pending tx ↔ approval URL ↔ user signature\n                            ▲\n                            │\n                            └── /approve/:id  →  Privy hosted signing  →  tx hash\n```\n\nWrite tools follow the **stored request pattern**:\n1. Tool builds an unsigned `{ to, value, data }` payload and a human-readable summary.\n2. Server stores it under a UUID and returns `https://your-server/approve/<uuid>` to the agent.\n3. User opens the URL — page shows the summary + asset diff + signs via their Privy embedded wallet.\n4. Server records the tx hash; agent polls `poll_request` and continues.\n\nThe agent never touches a private key. Privy's TEE-backed key custody + wallet policies are the security envelope.\n\n## Adding your own skill plugin\n\nPlugins live in `src/plugins/<id>/` and export a `SkillPlugin`:\n\n```ts\nimport type { SkillPlugin } from \"../types.js\";\nimport { registerTool } from \"../../tools/registry.js\";\n\nexport const myPlugin: SkillPlugin = {\n  id: \"myproto\",\n  name: \"MyProto on Monad\",\n  description: \"What it does in one sentence.\",\n  networks: [\"mainnet\", \"testnet\"],\n  register(mcp, server) {\n    registerTool(mcp, server, myReadTool);\n    registerTool(mcp, server, myWriteTool);\n  },\n};\n```\n\nRegister 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.\n\n## Testing\n\n```bash\nnpm test                # vitest unit tests (no network) — 56 tests\nnpm run test:integration  # integration tests against live testnet RPC + Privy\nnpm run typecheck\nnpm run lint\n```\n\n56 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.\n\nThe 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.\n\n## Security model\n\n- **Keys**: Privy holds the key in a TEE. The MCP server never sees a private key.\n- **Authorization to act**: every write tool builds a stored request; nothing happens until the user clicks Approve on the approval page.\n- **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.\n- **Stale link defense**: stored requests expire after `ttl_seconds` (default 5 min); expired requests can't be approved.\n- **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.\n\nFor 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.\n\n## Session keys: skipping per-tx approvals\n\nBy 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`:\n\n```\n> Grant the agent up to 0.5 MON for the next 24h to send to 0xabc…\n\nagent calls grant_session_key({\n  spend_cap_mon: \"0.5\",\n  ttl_seconds: 86400,\n  allowed_targets: [\"0xabc...\"],\n})\n→ returns an approval URL. User clicks once.\n\n> Now send 0.01 MON to 0xabc…\n\nagent calls transfer({ to: \"0xabc...\", amount: \"0.01\" })\n→ no approval URL. Tool returns the tx hash immediately,\n  grant remaining: 0.49 MON.\n```\n\nGrants are scoped by:\n- **`spend_cap_mon`** — total native MON the agent can spend (running total tracked across calls)\n- **`ttl_seconds`** — grant expiry window (max 30 days)\n- **`allowed_targets`** — optional recipient/contract allowlist (empty = any)\n- **`allowed_selectors`** — optional 4-byte function selector allowlist (e.g. `0xa9059cbb` = ERC-20 transfer only)\n\nCoverage 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`.\n\n**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.\n\n## End-to-end testing on Monad testnet\n\n```bash\n# 1. Provision a test user + embedded wallet (one-time, costs a Privy seat).\nnpm run e2e:create-user\n# Prints: user_id, wallet_address, faucet URL. Save user_id to .env.\n\n# 2. Fund the printed wallet at https://testnet.monad.xyz/\n\n# 3. Run the round-trip transfer test.\nnpm run e2e:transfer\n# Builds a transfer through runTool, signs+broadcasts via Privy, waits for receipt.\n\n# 4. Sanity-check Privy creds reach the API.\nnpm run verify:privy\n```\n\nThe e2e script reuses `MONAD_MCP_E2E_USER_ID` from env so you don't burn a new Privy seat on each run.\n\n## What's next\n\n- Browser-first signing path (Privy web SDK in the approval page → no server-side `sendTransaction` round-trip).\n- More plugins — Neverland lending (Aave V3 fork on Monad), additional DEXes/perps.\n- Block-explorer integration once Monad's explorer API stabilizes (`get_transaction_history` will return full decoded history).\n- x402 payment support (pay for x402-enabled services).\n- Per-token (ERC-20) spend caps on session grants — today's cap is a single native-MON budget per grant.\n- Universal contract reader/writer for \"talk to any Monad contract\" without writing a per-protocol plugin.\n\n## License\n\nMIT — see `LICENSE`.\n",
  "bytes": 16352,
  "sha": "4412899aca872838682e7a1dde7b4ed585ebcb725464654f4054ef241683ceaa",
  "repo_slug": "pareen/monad-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_pareen_monad_mcp_8aec34ca/readme"
}