mayan-mcp
Enables cross-chain swap queries and unsigned transaction building for Mayan Finance, supporting Solana and seven EVM chains via MCP tools, without holding private keys or signing transactions.
README
mayan-mcp
An MCP server for Mayan Finance cross-chain swaps.
Ask an AI assistant what a swap will cost, then get the exact transaction to sign — between Solana and seven EVM chains, through Mayan's Swift and MCTP routes.
This server holds no private keys. It cannot sign and it cannot broadcast. build_swap returns
an unsigned transaction; the caller signs it in their own wallet. There is no tool here that can
move funds.

Before any transaction is handed over, its own encoded arguments are read back and compared against the quote you were shown — the token, the amount, and the contract it forwards to. A payload that spends more than it quoted is refused rather than returned.
Those are real numbers, not a mockup: scripts/capture-demo.ts drives the built server over stdio
against the live API and writes what came back to docs/captured-output.json,
which is what the image above renders. Run it yourself and compare — a tool whose claim is "check
what you are signing" should be checkable itself.
Custody model
| Reads quotes, tokens, swap status | yes |
| Builds transactions | yes, unsigned |
| Holds private keys or seed phrases | no |
| Signs transactions | no |
| Broadcasts to a chain | no |
The guarantee is structural rather than procedural: there is no key material in the process, so there is no code path from a compromised prompt to a moved balance. The signing step belongs to the wallet, which is also where the user sees the recipient address one last time.
Two consequences worth stating plainly:
- Tool annotations are not the guarantee. This server sets
readOnlyHint, but the MCP specification requires clients to treat annotations from an untrusted server as untrusted. The claim that matters is "no keys in the process", which you can check by readingsrc/. - A prompt injection cannot move funds through this server, but it can still propose a bad swap. Every response echoes the recipient address, the minimum received and the slippage next to the payload, so the terms are visible before anyone signs.
See SECURITY.md for the threat model, the invariant checks, and a reachability
analysis of the current npm audit output.
Install
Requires Node.js 20 or newer.
npx @sand0vvv/mayan-mcp --help
Claude Desktop / Claude Code
{
"mcpServers": {
"mayan": {
"command": "npx",
"args": ["-y", "@sand0vvv/mayan-mcp"],
"env": {
"SOLANA_RPC_URL": "https://your-solana-rpc-endpoint"
}
}
}
}
Cursor / other MCP clients
Same command, over stdio: npx -y @sand0vvv/mayan-mcp.
Streamable HTTP
npx @sand0vvv/mayan-mcp --http --port 8787
Stateless — a fresh server per request, no session state. It binds to 127.0.0.1 and validates the
Host and Origin headers, so a page in your browser cannot reach it by pointing its own hostname at
loopback. There is no authentication: exposing it on another interface (--host 0.0.0.0) is a
deliberate act that belongs behind a proxy providing auth and rate limiting.
Configuration
Everything comes from the environment. Nothing is required to get a quote.
| Variable | Required | Purpose |
|---|---|---|
SOLANA_RPC_URL |
for Solana builds | Building a Solana-source swap needs a live RPC connection. Without it the server falls back to the public mainnet endpoint, which throttles hard. |
MAYAN_RPC_URL_<CHAIN> |
for allowance reads | Per-chain EVM endpoint, e.g. MAYAN_RPC_URL_BASE. Lets the server read your existing allowance instead of assuming an approve is needed. Read-only: the only call made is eth_call. |
MAYAN_API_KEY |
no | Lifts Mayan's per-IP rate limit. Request one from support@mayan.finance. |
MAYAN_HTTP_TIMEOUT_MS |
no | Default 30000. |
MAYAN_MAX_RETRIES |
no | Default 3. Retries are full-jitter backoff, reads only. |
MAYAN_TOKEN_CACHE_TTL_MS |
no | Default 600000. |
MAYAN_REFERRER_EVM / MAYAN_REFERRER_SOLANA |
no | Attaches a fee-split referrer to quotes. Unset by default, and left unset in this project — see SECURITY.md. |
MAYAN_MCP_DEBUG |
no | 1 for verbose logging on stderr. |
There is no variable for a private key, and there never will be.
Tools
| Tool | What it does |
|---|---|
get_quote |
Every available route for a swap: expected output, guaranteed minimum, slippage, fees, ETA. |
build_swap |
The unsigned transaction, plus the quote terms and any ERC20 approval needed first. |
list_tokens |
Tokens Mayan can route on a chain, with addresses and decimals. |
track_order |
Status of a swap by its source-chain transaction hash. |
check_allowance |
How much the Mayan forwarder is currently allowed to spend, and whether that covers a swap. |
list_chains |
Supported chains and their EVM chain ids. |
estimate_gas |
Gas estimate for a built EVM transaction. |
Supported chains: solana, ethereum, base, arbitrum, optimism, polygon, bsc,
avalanche.
Worked example
"What do I get for 0.5 SOL into USDC on Base?"
get_quote → two routes:
0.5 SOL on solana -> USDC on base
SWIFT V2 · Swift
expected 37.137748 USDC
minimum 36.933584 USDC (slippage 55 bps)
eta 3s
price GOOD (ratio 0.9996)
quote id 0xa4c71a85927b104fe039e05d56efe944
FAST_MCTP · Best Price
expected 37.155133 USDC
minimum 36.954199 USDC (slippage 50 bps)
eta 35s
price GOOD (ratio 0.9996)
"Build it."
build_swap → the transaction, unsigned:
Unsigned swap built: 10 USDC on base -> SOL on solana via SWIFT
You receive at least 0.130172143 SOL (expected 0.134198086, slippage 300 bps, ETA 3s)
Recipient: 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
Transaction to sign:
to 0x337685fdaB40D39bd02028545a4FfA7D287cC3E2
chainId 8453
value 0x0
data 932 bytes (forwardERC20)
Checked before returning it:
ok target is the Mayan forwarder from the SDK constant
ok chain id matches the quote
ok the call spends USDC, the token quoted
ok the call spends 10000000 base units, the amount quoted
ok forwards to 0x40fFE85A28DC9993541449464d7529a922142960, the SWIFT contract the quote declared
Approval required BEFORE this transaction:
approve 10000000 (base units) of USDC
spender 0x337685fdaB40D39bd02028545a4FfA7D287cC3E2 (Mayan forwarder)
Signers returned: 0. Unsigned. This server holds no private keys and cannot sign or broadcast.
Those checks are not decoration. The transaction is assembled from data Mayan's backend served, so the SDK's own encoded arguments are read back and compared against the quote you were shown: the token, the amount, and the protocol contract. A payload that spends more than it quoted, or routes through a substituted contract, is refused rather than returned. See SECURITY.md.
structuredContent carries the same thing machine-readably: evm_transaction, approval, route
and a verification block recording which invariants were checked.
Solana output
Two things are different when the source chain is Solana, both stated here rather than left to be discovered:
- The cross-check above does not apply. It reads an EVM call's encoded arguments; a Solana build is a list of instructions with no such envelope. Those fields are therefore absent from the verification block rather than reported as failures. What still holds is the route allow-list and the empty signer set — the custody claim itself. Review the instructions in your wallet.
MCTPfrom Solana cannot be built here. That route needs an ephemeral keypair to carry its CCTP message, and this server does not emit key material of any kind.SWIFTfrom Solana builds without one and is what an unpinnedbuild_swapselects.
A Solana-source swap returns instructions in the SDK's own InstructionInfo shape, so the output
feeds straight back into deserializeInstructionInfo() without a translation step:
{
"programId": "ComputeBudget111111111111111111111111111111",
"accounts": [],
"data": "AsBcFQA="
}
Address lookup tables come back as base58 addresses, ready for
connection.getMultipleAccountsInfo().
What the caller still has to do
- Send the approval first on EVM sources, or the swap reverts.
build_swaptells you the token, the amount and the spender. WithMAYAN_RPC_URL_<CHAIN>set it reads your current allowance and says nothing when you have already approved enough. (USDT on Ethereum additionally requires zeroing an existing allowance before raising it — the response says so when it applies.) - Sign and submit. Not this server's job, by design.
- Check the recipient address in your wallet before signing.
Development
npm install
npm run build
npm test # unit tests: invariants, serialisation round-trip, schemas, layering
npm run typecheck
npx tsx scripts/smoke.ts # live: quotes and builds against Mayan's API
npx tsx scripts/e2e.ts # live: spawns the built server and speaks MCP over stdio
The layout keeps one boundary strictly:
src/client/ the only code that talks to @mayanfinance/swap-sdk
src/tools/ MCP tool definitions — Zod in, structuredContent out
src/transport/ stdio and Streamable HTTP
test/layering.test.ts fails the build if anything above src/client imports the SDK or a chain
library directly, and it verifies its own detector fires before trusting the result.
License
MIT. See LICENSE.
Not affiliated with Mayan Finance. This is client software for their public API; it makes no warranty about routing, pricing or settlement, and it is not financial advice.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。