MetaLend
REMOTE · MCP.METALEND.TECH · SCANNED SEP 20
Browse, deposit, withdraw & rebalance stablecoins on Aave, Morpho & Euler across major EVM chains.
Available components
How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. How we score → Why this is hard to score →
Endpoint Security63
- The endpoint's TLS certificate is valid, in date, and uses a strong key. View diagnostics → Pass
- Authorisation check failed: no authorisation is required to call this server, and it exposes a tool marked destructive (submit_deposit). See how to fix → View diagnostics → Fail
- HTTPS is enforced; there's no plaintext access path. View diagnostics → Pass
- The HSTS (Strict-Transport-Security) header is present. View diagnostics → Pass
- DNSSEC check failed: this domain isn't protected by DNSSEC. See how to fix → View diagnostics → Fail
Transport & Reachability100
- Verified streamable-http transport via a live MCP handshake. View diagnostics → Pass
Schema Quality & AI Usability75
- 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).Pass
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 9925 tokens (~472/item across 21 items; 19 tools + 2 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management80
- Stability observed for 24 of 30 days with no destabilising changes; credit accrues until the full window elapses.Partial
Tool Coverage100
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 99% of tool parameters carry a description.Partial
Tool Safety100
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- We read all 19 captured tool definition(s), and no name or description among them implies an irreversible operation.Pass
- An AI judge read all 21 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
How do I install the MetaLend MCP server?
MetaLend is a hosted endpoint at https://mcp.metalend.tech/mcp, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
remote · mcp.metalend.tech
claude mcp add --transport http rabeles11-metalend 'https://mcp.metalend.tech/mcp'
{
"mcpServers": {
"rabeles11-metalend": {
"url": "https://mcp.metalend.tech/mcp"
}
}
} {
"servers": {
"rabeles11-metalend": {
"type": "http",
"url": "https://mcp.metalend.tech/mcp"
}
}
} [mcp_servers.rabeles11-metalend] url = "https://mcp.metalend.tech/mcp"
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"rabeles11-metalend": {
"type": "remote",
"url": "https://mcp.metalend.tech/mcp",
"enabled": true
}
}
} openclaw mcp add rabeles11-metalend --url 'https://mcp.metalend.tech/mcp' --transport streamable-http
mcp_servers:
rabeles11-metalend:
url: "https://mcp.metalend.tech/mcp" {
"McpServers": {
"rabeles11-metalend": {
"Transport": "http",
"Url": "https://mcp.metalend.tech/mcp"
}
}
} assistant mcp add rabeles11-metalend -t streamable-http -u 'https://mcp.metalend.tech/mcp'
{
"mcpServers": {
"rabeles11-metalend": {
"type": "http",
"url": "https://mcp.metalend.tech/mcp"
}
}
} The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.
Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 20 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 77 to 80. That category is still filling its 30-day observation window: 23 days of observed history at the previous scan, 24 at this one. The score rises as the window fills, whether or not the server changes.
- 17 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 67 to 70. That category is still filling its 30-day observation window: 20 days of observed history at the previous scan, 21 at this one. The score rises as the window fills, whether or not the server changes.
- 15 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 60 to 63. That category is still filling its 30-day observation window: 18 days of observed history at the previous scan, 19 at this one. The score rises as the window fills, whether or not the server changes.
- 13 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 53 to 57. That category is still filling its 30-day observation window: 16 days of observed history at the previous scan, 17 at this one. The score rises as the window fills, whether or not the server changes.
- 11 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 47 to 50. That category is still filling its 30-day observation window: 14 days of observed history at the previous scan, 15 at this one. The score rises as the window fills, whether or not the server changes.
- 9 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 40 to 43. That category is still filling its 30-day observation window: 12 days of observed history at the previous scan, 13 at this one. The score rises as the window fills, whether or not the server changes.
- 7 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 33 to 37. That category is still filling its 30-day observation window: 10 days of observed history at the previous scan, 11 at this one. The score rises as the window fills, whether or not the server changes.
- 5 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 27 to 30. That category is still filling its 30-day observation window: 8 days of observed history at the previous scan, 9 at this one. The score rises as the window fills, whether or not the server changes.
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 20 Sept 2026 · Probed https://mcp.metalend.tech/mcp
TLS valid
Negotiated TLS 1.3 with TLS_AES_128_GCM_SHA256 .
| Subject | Issuer | Valid from | Valid until | Key | Signature | Serial |
|---|---|---|---|---|---|---|
| CN=metalend.tech | CN=WE1,O=Google Trust Services,C=US | 4 Aug 2026 | 2 Nov 2026 | ECDSA 256 | ECDSA-SHA256 | f7fd193f75f951530ee44f1ce0210368 |
| SANs: metalend.tech, *.metalend.tech, *.app.metalend.tech, *.base.metalend.tech, *.developer.metalend.tech, *.dev.metalend.tech | ||||||
| CN=WE1,O=Google Trust Services,C=US (CA) | CN=GTS Root R4,O=Google Trust Services LLC,C=US | 13 Dec 2023 | 20 Feb 2029 | ECDSA 256 | ECDSA-SHA384 | 7ff31977972c224a76155d13b6d685e3 |
| CN=GTS Root R4,O=Google Trust Services LLC,C=US (CA) | CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE | 15 Nov 2023 | 28 Jan 2028 | ECDSA 384 | SHA256-RSA | 7fe530bf331343bedd821610493d8a1b |
Background: What to check on a remote MCP endpoint →
DNSSEC insecure
Validation of mcp.metalend.tech. — Not signed
| Zone | DS | Keys | Algorithms | Outcome |
|---|---|---|---|---|
| . | trust_anchor | 20326, 38696 | 8, 8 | Verified |
| tech. | present | 2185 | 13 | Verified |
| metalend.tech. | absent | Unsigned (proven) parent-signed NSEC/NSEC3 proves an unsigned delegation |
Authentication No authorisation required
The endpoint answered without asking for a token. Anyone who knows the URL can reach it.
| Result | No authorisation required |
|---|---|
| HTTP status | 200 |
| Header | Value |
|---|---|
| strict-transport-security | max-age=31536000; includeSubDomains; preload |
| x-content-type-options | nosniff |
| x-frame-options | DENY |
| referrer-policy | strict-origin-when-cross-origin |
| permissions-policy | geolocation=(), microphone=(), camera=() |
Background: How OAuth 2.1 works in the 2026 MCP spec →
Transports 2 probes
| Transport | URL | Outcome | Status | Location |
|---|---|---|---|---|
| streamable-http | https://mcp.metalend.tech/mcp | Verified | 200 | |
| http (plaintext) | http://mcp.metalend.tech/mcp | HTTPS enforced | 301 | https://mcp.metalend.tech/mcp |
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
get_auth_challenge Get SIWE auth challenge ~281
Get a Sign-In-With-Ethereum challenge message for a wallet, required before any deposit/withdrawal/config-update tool. The returned `message` must be signed with personal_sign (EIP-191) by the wallet's own signer, then passed to submit_auth_verify. Does NOT itself authenticate anything — it only issues the message to sign. Repeated calls for the same walletAddress within a short window (a few minutes) return the SAME message rather than a fresh one — the backend only keeps one pending challenge per wallet at a time, and generating a new one would invalidate whatever an earlier caller is about to sign, so this is deliberate, not a caching bug. `chain` does not affect which cached message you get back. Rate limited to 10 calls/minute per caller, no more than one call every 6s.
| Name | Type | Req | Description |
|---|---|---|---|
| chain | string | – | Chain name, e.g. BASE, ETHEREUM, POLYGON. Defaults to ETHEREUM if omitted. Cosmetic only — shown as the 'Chain ID:' line in the SIWE message text, does not need to match the chain passed to submit_au… |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
get_balances Get rebalancer balances ~351
Get a wallet's deployed rebalancer balances broken down per token, per chain, and per protocol/pool, including net earnings and blended APY. Also returns the wallet's rebalancerAddress (required only for approvals during deposits, never use this address as input into tools) and per-pool withdrawRequest EIP-712 data. Does NOT return rebalancingManagerAddress (needed for config signing) — use get_config for that. Does NOT return pool catalog/APY data for pools the wallet isn't in — use list_pools for that. Fails if the wallet has no rebalancer yet; use get_default_config to see what a first-time config would look like. Balances here already reflect every on-behalf gas fee paid so far — not just from this wallet's own deposits/withdrawals, but also from MetaLend automatically moving funds between pools/protocols/chains to chase yield or honor this wallet's config; each such move costs its own gas fee. So the total across pools can be slightly less than the sum of everything ever deposited, with no matching withdrawal — see get_config's `totalGasFee` for the lifetime total. Rate limited to 5 calls/minute per caller, no more than one call every 12s.
| Name | Type | Req | Description |
|---|---|---|---|
| tokens | string | – | Comma-separated token symbols to restrict to (e.g. 'USDC,USDT'). Omit for all supported tokens. |
| walletAddress | string | yes | The user's own wallet address (EOA or smart-contract wallet) — the owner address they used to make deposits. This is NOT the rebalancerAddress that this tool returns: never pass a rebalancerAddress h… |
No output schema declared.
No examples provided.
get_bridge_balances Get bridge balances ~219
Get a wallet's in-transit USDC bridge balances (funds moving cross-chain toward a destination pool), including estimated completion time. Does NOT include already-settled rebalancer balances — use get_balances for those. Only applies to USDC. Other tokens do not support bridging. Each entry's `sourcePool` is null for a cross-chain deposit in transit, or a populated pool for an internal rebalance moving funds between pools; `isForSpending: true` marks a funding-cap refill specifically. There is NO trackingId on these entries — they cannot be correlated back to a specific submit_deposit call; use get_deposit_status with the trackingId from that call instead to track one particular deposit. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
| Name | Type | Req | Description |
|---|---|---|---|
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
get_config Get rebalancer config ~524
Get a wallet's current rebalancer configuration per token: which protocols/pools/chains are enabled, rebalance frequency, lifetime deposits/withdrawals/fees, and spending cap. `spendingCapRaw`/`spendingCapFormatted` (USDC only, null/"0" for every other token) is NOT a spending/deposit/withdrawal limit despite the name. When set (nonzero), it's the target balance MetaLend automatically keeps topped up in the wallet's OWN address (not the rebalancer contract) as Aave aUSDC + native USDC on Linea combined — funding a card tied to this wallet that spends directly from that Linea balance. Refills happen automatically on a weekly cycle and after fresh USDC deposits, not instantly. null/"0" means this auto-funding is off, not "uncapped" (there is no general spending ceiling to remove) — see prepare_config's own spendingCapRaw field description for the full mechanics. Also returns rebalancerAddress and rebalancingManagerAddress. Does NOT return live balances — use get_balances for that. `rebalancerExists: false` means the wallet has never configured a rebalancer; use get_default_config to see recommended starting values. Each configuration's `totalGasFee` is the lifetime sum of on-behalf gas fees this wallet has paid for this token — not just from deposits/withdrawals it explicitly requested. MetaLend periodically moves a wallet's funds between pools/protocols/chains on its own (chasing better yield, honoring `requiredTvl`/`requiredLiquidityMultiplier`/`collateralExposure`), and each such move is its own on-chain transaction with its own gas fee, deducted the same way a deposit/withdrawal fee is. So a wallet's balance can drift down slightly between get_balances polls with no deposit/withdrawal in between — that's this, not a bug or lost funds. IMPORTANT: `configurations` always includes a default/template entry for every supported token, whether or not the wallet ever signed one — a matching entry existing does NOT mean the wallet has a real config. Check that entry's `ha…
| Name | Type | Req | Description |
|---|---|---|---|
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
get_default_config Get default rebalancer config for a token ~123
Get MetaLend's recommended default rebalancer configuration for a given token symbol (e.g. 'USDC'): rebalancingManagerAddress plus recommended protocolIds/poolAddresses/domainIds. Intended for wallets with no existing config yet — for an existing wallet's current config use get_config instead. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
| Name | Type | Req | Description |
|---|---|---|---|
| token | string | yes | Token symbol, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
No output schema declared.
No examples provided.
get_deposit_status Get deposit status ~280
Poll the status of a deposit by trackingId (returned by submit_deposit). `status` is one of: PROCESSING — still confirming on-chain, keep polling; BRIDGING — deposit accepted, funds are now crossing chains via CCTP to the target pool (typically 20-30 minutes, up to several hours from Linea) — treat this as done, do NOT keep polling waiting for the bridge to finish; SUCCESS — terminal, deposit fully completed (reached directly for a same-chain deposit, or later for a bridged one once funds land — you do not need to wait for it after BRIDGING); FAILED — terminal, deposit did not go through; EMERGENCY_WITHDRAW — terminal but NOT success: after the bridge completed and funds reached the destination chain, the destination pool could not accept the deposit (e.g. it was at capacity), so funds were automatically sent back directly to your OWN wallet address — not to the rebalancer, not lost or stuck. This resolution (bridge completion plus destination-side processing) can itself take 30+ minutes after BRIDGING is first observed, so don't expect it right away. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
| Name | Type | Req | Description |
|---|---|---|---|
| trackingId | string | yes | trackingId returned by submit_deposit. |
No output schema declared.
No examples provided.
get_rewards Get rebalancer rewards ~314
Get a wallet's aggregated rewards/earnings across all reward sources (e.g. Merkle), including claimed/available USD totals. Does NOT include base rebalancer yield/APY earnings — those are in get_balances' netEarning field. There is NO backend endpoint to submit a claim — this server cannot claim rewards for you. Each rewards[].rewardItems[] entry with an available balance carries an optional claimTransaction (chain-specific, per RewardItem.chain) with everything needed to broadcast the claim directly: `to` (send the transaction here — this is MetaLend's RebalancingManager contract, NOT the same item's own `distributorAddress`, which is only an input parameter, not the call target), `abi`, and pre-encoded `calldata`. Your own wallet must sign and broadcast this on-chain (this server has no RPC access and cannot do it for you) — if claimTransaction is absent on an item, treat it as nothing currently claimable there (e.g. still vesting) rather than assuming one should exist. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
| Name | Type | Req | Description |
|---|---|---|---|
| reloadChain | string | – | Chain name to force a fresh reload for (e.g. 'BASE'). Omit to use cached data. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
get_token_info Get token info ~126
Get a token's on-chain metadata (display name, decimals, EIP-712 version) for a given chain. Needed when building an EIP-3009 deposit signature's tokenName/tokenVersion fields. Rate limited to 6 calls/minute per caller, no more than one call every 10s.
| Name | Type | Req | Description |
|---|---|---|---|
| chain | string | yes | Chain name, e.g. BASE, ETHEREUM, POLYGON. |
| token | string | yes | Token symbol, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
No output schema declared.
No examples provided.
get_transaction_costs Get transaction costs ~104
Get constant on-behalf deposit/withdraw gas costs and minimum deposit/withdraw amounts for a token, broken down per supported chain. Use before a deposit to check the amount meets the chain's minimum. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
| Name | Type | Req | Description |
|---|---|---|---|
| token | string | yes | Token symbol, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
No output schema declared.
No examples provided.
get_withdrawal_status Get withdrawal status ~111
Poll the status of a withdrawal by trackingId (returned by submit_withdrawal). `status` is one of: PROCESSING — still in progress, keep polling (withdrawals have no cross-chain bridging leg, unlike deposits); SUCCESS — terminal, withdrawal completed; FAILED — terminal, withdrawal did not go through. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
| Name | Type | Req | Description |
|---|---|---|---|
| trackingId | string | yes | trackingId returned by submit_withdrawal. |
No output schema declared.
No examples provided.
get_withdrawal_version Get withdrawal contract version ~63
Get the contract signing version string required for the EIP-712 domain data used when signing a withdrawal. Rarely changes; safe to cache client-side. Rate limited to 10 calls/minute per caller, no more than one call every 6s.
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
list_pools List rebalancer pools ~239
List all pools MetaLend's rebalancer can deposit into: protocol (Aave, Morpho, Euler), chain, APY breakdown (native/rewards/total/net-of-fee), TVL, liquidity, and the signData (protocolId, poolAddress, domainId) needed to build a rebalancer config. Does NOT return a wallet's current balances or configuration — use get_balances / get_config for that. Callers should filter out pools where `blacklisted` is true. `poolTvl`/`poolLiquidity` can read "0" either because the pool is genuinely empty or because upstream data is momentarily missing — the two are not distinguishable here. There is no pool-level paused/capacity/deposit-or-withdraw-enabled flag: deposits can be paused globally, and withdrawals from specific pools can be temporarily blocked per-wallet (e.g. during a funding-cap refill) — neither shows up in this list, only as an error from submit_deposit/submit_withdrawal itself. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
prepare_config Prepare a rebalancer config update ~1,979
Build the signing hash for updating a wallet's rebalancer configuration (which pools/protocols/chains it's allowed to move funds into), without signing anything. Computes keccak256(abi.encode(managerAddress, protocolIds, poolAddresses, domainIds, spendingCapRaw)) server-side — sign the returned hashToSign with personal_sign over its raw 32 bytes (not the UTF-8 text of the hex string) and pass the signature to submit_config. Refuses to build a config that would drop a pool you still hold a nonzero balance in — withdraw from it first (prepare_withdrawal) and retry. Also refuses an invalid `spendingCapRaw` (see its own field description for the exact rules), a non-null `collateralExposure` with no Aave pool included, containing an untracked symbol, or where no requested Aave pool's live TVL meets requiredTvl, any (domainId, protocolId, poolAddress) tuple that doesn't match a real pool in the current catalog for this token (see list_pools), or a config where every requested pool is blacklisted (see list_pools' `blacklisted` field) — a deposit under such a config would have no eligible destination and fail later, well after signing — rather than building a hash for it. For a smart-contract wallet: there is no single `chain` for this signature — the backend derives candidate chains straight from `domainIds` and requires the SAME signature to independently pass ERC-1271/ERC-6492 verification on EVERY chain named in domainIds, not just one; if the wallet doesn't have (deployed, or via ERC-6492 counterfactual deployment) a valid signer on all of them, the whole update is rejected. That signature must also be cross-chain transferable — supporting ERC-6492 counterfactual deployment does not by itself guarantee that. Some ERC-6492-compliant wallets intentionally bind their signature to a single network domain and are not cross-chain transferable — e.g. Coinbase's Smart Wallet (Base Smart Wallet) supports ERC-6492 but scopes its signature to one chain, so it does not support cr…
| Name | Type | Req | Description |
|---|---|---|---|
| collateralExposure | – | – | Whitelist of collateral asset symbols — applies only to Morpho vault selection; a vault is excluded from rebalancing if its own tracked collateral exposure includes any symbol not in this list. Each… |
| domainIds | array | yes | Chain domain IDs, one per poolAddresses entry, from list_pools' signData.domainId. Max 200 entries. For a smart-contract wallet, every distinct chain named here must independently verify the SAME sig… |
| includeRewardsApy | boolean | – | Whether reward-token (e.g. Merkle) APY counts toward the total-APY comparison used to pick the best pool to rebalance into — false restricts the comparison to native lending APY only. Defaults to tru… |
| poolAddresses | array | yes | Pool contract addresses to allow, from list_pools' signData.poolAddress. Max 200 entries. |
| protocolIds | array | yes | 0=Aave, 1=Morpho, 2=Euler — one entry per poolAddresses/domainIds entry, from list_pools' signData.protocolId. Pass only the exact set of (domainId, protocolId, poolAddress) pools you intend to allow… |
| requiredLiquidityMultiplier | number|string | – | Requires a candidate pool's available liquidity to be at least this multiplier times the deposit amount (USD) before it's eligible as a rebalance target — a liquidity safety margin, not a percentage.… |
| requiredTvl | number|string | – | Minimum pool TVL in USD required for a pool to be eligible as a rebalance target — pools below this are skipped when the rebalancer picks where to move funds. 0 (default if omitted) disables the filt… |
| spendingCapRaw | – | – | Raw units, USDC only (must be "0"/null/omitted for every other token). "0" means this feature is off (the default if omitted or null — get_config's own response serializes it as null, pass that strai… |
| token | string | yes | Token symbol this config applies to, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
prepare_deposit Prepare a deposit ~685
Build everything needed to deposit into MetaLend's rebalancer, without signing anything. For gasless tokens (USDC, MUSD, PYUSD) returns an EIP-712 ReceiveWithAuthorization typed-data payload — sign it with your own wallet and pass the signature to submit_deposit. For approval-only tokens (USDT, RLUSD, USDG, USDE) returns on-chain approve() parameters instead — your wallet must broadcast that approval itself (this server has no RPC access and cannot do it for you), then call submit_deposit with no signature. The signature flow only accepts a raw 65-byte EOA-style ECDSA signature — smart-contract wallets (including via ERC-6492 counterfactual deployment) are rejected regardless of validity, even for gasless-eligible tokens. A smart-contract wallet should instead pass `method: "approval"` explicitly here (works for any token, needs no signature at all) — but `chain` must then be one that wallet can actually transact on (see `chain`'s own field description); this server cannot validate that. Requires a signed rebalancer config for this token already (use prepare_config/submit_config first if get_config shows none) — and validates the amount against the chain's minimum deposit — before returning anything, so a doomed request never reaches signing. A fixed on-behalf gas fee (see `fee` in the response, also available standalone via get_transaction_costs) is deducted from `amount` before the rebalancer credits it — the response's `expectedCreditedAmountRaw` is what will actually show up in get_balances after the deposit lands, not the full `amount` you send. Rate limited to 6 calls/minute per caller, no more than one call every 10s.
| Name | Type | Req | Description |
|---|---|---|---|
| amount | string | yes | Raw token amount as a decimal-digit string (smallest denomination). Use get_token_info for decimals. |
| chain | string | yes | Chain name, e.g. BASE, ETHEREUM, POLYGON — the originating chain of this deposit: where the approve() call is broadcast, or for the signature flow, the chain the EIP-712 signature must validate on. N… |
| method | string | – | Force a specific flow. Defaults to 'signature' for gasless-eligible tokens, else 'approval'. |
| token | string | yes | Token symbol, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
prepare_withdrawal Prepare a withdrawal ~509
Build the EIP-712 typed-data payload to sign for withdrawing from a specific pool, without signing anything. Looks up the exact withdrawRequest domain/types/value MetaLend expects from your current balances, fills in amount and a short-lived deadline, and returns it ready to sign with your own wallet. Pass the resulting signature to submit_withdrawal. Omit `amount` (or pass "MAX") to withdraw the pool's entire balance — this uses the maxUint256 convention rather than the exact current balance, which avoids stale-balance/rounding failures. `chain` is required: the same pool contract address can exist on multiple chains (e.g. Aave reuses one address across Polygon/Arbitrum/Avalanche/Optimism), so poolContract alone cannot disambiguate a wallet holding balance in that pool on more than one of those chains. Also refuses to build typedData when the pool's live liquidity (see list_pools' poolLiquidity) does not exceed the amount being withdrawn (this also applies to MAX, using the pool's own balance) — the backend would reject the signed request either way, so this catches it before a wallet signs. A fixed on-behalf gas fee (see `fee` in the response, also available standalone via get_transaction_costs) is deducted from the withdrawn amount before it reaches your wallet — the response's `expectedReceivedAmountRaw` is what will actually arrive, not the full `amount` being withdrawn from the pool. Rate limited to 5 calls/minute per caller, no more than one call every 12s.
| Name | Type | Req | Description |
|---|---|---|---|
| amount | – | – | Raw amount to withdraw. Omit or pass "MAX" for a full withdrawal (uses maxUint256). |
| chain | string | yes | Chain name, e.g. BASE, ETHEREUM, POLYGON. |
| poolContract | string | yes | Pool contract address, from get_balances' perPool[].poolAddress. Combined with `chain` to identify the exact balance — the same address can exist on several chains. |
| token | string | yes | Token symbol, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. Required to disambiguate — some pool addresses serve multiple tokens. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
submit_auth_verify Verify SIWE signature and get a JWT ~349
Exchange a signed SIWE challenge message (from get_auth_challenge) for a JWT scoped to that wallet. The returned `jwt` must be passed explicitly as the `jwt` argument to submit_deposit/submit_withdrawal/submit_config for that same wallet — this server does not cache or store it. The JWT is only valid for the walletAddress that produced the signature; using it for a different wallet's write call will be rejected upstream. Reuse the same jwt for subsequent write calls to this wallet instead of re-authenticating every time — the response's `expiresAt` (decoded from the JWT's own exp claim) says how long it's good for. Rate limited to 5 calls/minute per caller, no more than one call every 12s.
| Name | Type | Req | Description |
|---|---|---|---|
| chain | string | – | Chain name, e.g. BASE, ETHEREUM, POLYGON. Defaults to ETHEREUM if omitted. For an EOA wallet this is irrelevant (signature recovery is chain-agnostic). For a smart-contract wallet, this MUST be the c… |
| signature | string | yes | personal_sign signature of the SIWE message from get_auth_challenge. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
submit_config Submit a rebalancer config update ~1,578
Submit a signed rebalancer configuration update. Requires a `jwt` from submit_auth_verify for this walletAddress and a `signature` over prepare_config's hashToSign. Always call prepare_config first — this tool does not compute the signing hash or check the balance invariant itself. Can return a 409 if a rebalance or deposit is currently in progress for this wallet+token, or (USDC only) a funding-cap refill is in progress — this is unrelated to the signature and not something prepare_config could have caught; wait and retry (the error body includes a retryAfterSeconds hint). This call can also time out on its own if the backend accepted the update but its response took too long to arrive — if that happens, check get_config afterward rather than assuming it didn't happen. Rate limited to 10 calls/minute per caller, no more than one call every 6s.
| Name | Type | Req | Description |
|---|---|---|---|
| collateralExposure | – | – | Whitelist of collateral asset symbols — applies only to Morpho vault selection; a vault is excluded from rebalancing if its own tracked collateral exposure includes any symbol not in this list. Each… |
| domainIds | array | yes | Chain domain IDs, one per poolAddresses entry, from list_pools' signData.domainId. Max 200 entries. For a smart-contract wallet, every distinct chain named here must independently verify the SAME sig… |
| includeRewardsApy | boolean | – | Whether reward-token (e.g. Merkle) APY counts toward the total-APY comparison used to pick the best pool to rebalance into — false restricts the comparison to native lending APY only. Defaults to tru… |
| jwt | string | yes | JWT from submit_auth_verify for this walletAddress. |
| poolAddresses | array | yes | Pool contract addresses to allow, from list_pools' signData.poolAddress. Max 200 entries. |
| protocolIds | array | yes | 0=Aave, 1=Morpho, 2=Euler — one entry per poolAddresses/domainIds entry, from list_pools' signData.protocolId. Pass only the exact set of (domainId, protocolId, poolAddress) pools you intend to allow… |
| requiredLiquidityMultiplier | number|string | – | Requires a candidate pool's available liquidity to be at least this multiplier times the deposit amount (USD) before it's eligible as a rebalance target — a liquidity safety margin, not a percentage.… |
| requiredTvl | number|string | – | Minimum pool TVL in USD required for a pool to be eligible as a rebalance target — pools below this are skipped when the rebalancer picks where to move funds. 0 (default if omitted) disables the filt… |
| signature | string | yes | Signature over prepare_config's hashToSign. |
| spendingCapRaw | – | – | Raw units, USDC only (must be "0"/null/omitted for every other token). "0" means this feature is off (the default if omitted or null — get_config's own response serializes it as null, pass that strai… |
| token | string | yes | Token symbol this config applies to, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
submit_deposit Submit a deposit ~1,026
Submit a deposit to MetaLend's rebalancer — moves funds. Requires a `jwt` from submit_auth_verify for this walletAddress, and either a `signature` (from prepare_deposit's signature-method output) or, for approval-based tokens, no signature at all once your wallet has already broadcast the approve() transaction on-chain. Always call prepare_deposit first — this tool does not validate amounts or resolve addresses itself (it does reject a partial set of validAfter/validBefore/nonce/signature/tokenName/tokenVersion — provide all six, or none). This call can time out on its own if the backend accepted the deposit but its response took too long to arrive. For a signature-based deposit, the trackingId needed for get_deposit_status is still recoverable then: retry with the exact same signature/nonce — it cannot double-spend (a real duplicate is rejected with a 409 signature-already-used error, which itself confirms the original deposit went through), and that error's message now includes the original trackingId, so read it from there and call get_deposit_status directly rather than escalating to support. If that error's message doesn't include a trackingId (it's only included when the conflicting deposit belongs to this same wallet), treat the timeout as inconclusive rather than a failure and escalate to support with the wallet address and approximate time instead. For an approval-based deposit (no signature), retrying after an uncertain outcome is safe in the ordinary case without needing to check status first: this tool's own `amount` is only used by the backend to check against your current on-chain allowance before anything is broadcast — the on-chain deposit transaction itself takes no amount parameter, it just pulls and consumes your entire then-current allowance atomically. So if the original attempt already landed, a retry finds a zero allowance and is rejected outright with a 400 ApprovalValidationError from that same pre-flight check, before anything is broadcast…
| Name | Type | Req | Description |
|---|---|---|---|
| amount | string | yes | Raw token amount as a decimal-digit string (smallest denomination). Use get_token_info for decimals. |
| chain | string | yes | Chain name, e.g. BASE, ETHEREUM, POLYGON — the originating chain of this deposit: where the approve() call is broadcast, or for the signature flow, the chain the EIP-712 signature must validate on. N… |
| jwt | string | yes | JWT from submit_auth_verify for this walletAddress. |
| nonce | string | – | From prepare_deposit's signature-method output. |
| signature | string | – | Signature over prepare_deposit's typedData. Omit for approval-based deposits. |
| token | string | yes | Token symbol, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
| tokenName | string | – | From prepare_deposit's signature-method output. |
| tokenVersion | string | – | From prepare_deposit's signature-method output. |
| validAfter | string | – | From prepare_deposit's signature-method output. |
| validBefore | string | – | From prepare_deposit's signature-method output. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
submit_withdrawal Submit a withdrawal ~535
Submit a withdrawal from MetaLend's rebalancer — moves funds. Requires a `jwt` from submit_auth_verify for this walletAddress and a `signature` over prepare_withdrawal's typedData. Always call prepare_withdrawal first — this tool does not look up pool data or validate amounts. The signature's EIP-712 domain is scoped to this one `chain` — for a smart-contract wallet, it must have (or, if undeployed, would have via ERC-6492 counterfactual deployment) a valid signer on that exact chain (an EOA is chain-agnostic and unaffected). This call can time out on its own if the backend accepted the withdrawal but its response took too long to arrive — the trackingId needed for get_withdrawal_status is still recoverable then: retry with the exact same signature — it cannot double-spend (a real duplicate is rejected with a 409 signature-already-used error, which itself confirms the original withdrawal went through), and that error's message now includes the original trackingId, so read it from there and call get_withdrawal_status directly rather than escalating to support. If that error's message doesn't include a trackingId (it's only included when the conflicting withdrawal belongs to this same wallet), treat the timeout as inconclusive rather than a failure and escalate to support with the wallet address and approximate time instead. Can also fail with a 403 if the jwt wasn't issued for this walletAddress, or a 409 if withdrawing from the Linea Aave USDC pool specifically while a funding-cap refill for that wallet is in progress there (temporary; not visible ahead of time from list_pools or get_balances) — wait and retry. Rate limited to 20 calls/minute per caller, no more than one call every 3s.
| Name | Type | Req | Description |
|---|---|---|---|
| amount | string | yes | From prepare_withdrawal's output. |
| chain | string | yes | From prepare_withdrawal's output. |
| deadline | string | yes | From prepare_withdrawal's output. |
| jwt | string | yes | JWT from submit_auth_verify for this walletAddress. |
| poolContract | string | yes | – |
| signature | string | yes | Signature over prepare_withdrawal's typedData. |
| token | string | yes | Token symbol, e.g. USDC, USDT, MUSD, RLUSD, USDG, USDE, PYUSD. |
| walletAddress | string | yes | EVM wallet address (0x-prefixed, 40 hex chars). Any casing is accepted — it is normalized to its EIP-55 checksum before being forwarded upstream. |
No output schema declared.
No examples provided.
What is the MetaLend MCP server?
MetaLend is an MCP server listed in the public MCP registry as io.github.rabeles11/metalend. Browse, deposit, withdraw & rebalance stablecoins on Aave, Morpho & Euler across major EVM chains. This page covers its hosted endpoint (https://mcp.metalend.tech/mcp).
Is the MetaLend MCP server safe to use?
MetaLend scores 78 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the MetaLend MCP server expose?
MetaLend exposes 19 tools: list_pools, get_balances, get_bridge_balances, get_config, get_default_config, and 14 more. Their descriptions and schemas cost roughly 9,396 tokens of context every time the server is loaded.
Does the MetaLend MCP server require authentication?
No. We connected to MetaLend without credentials and it answered, so anything it exposes is reachable by anyone who knows the address.
Is the MetaLend MCP server still maintained?
MetaLend is still listed as active in the MCP registry. We last reached this channel on 20 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.