Skip to main content
Glama
hjbest007-lang

btc-wallet-mcp

wallet-mcp

Disclaimer — experimental, unaudited software. Use at your own risk. There is no warranty (see the MIT license). This is not financial, legal, or tax advice. You are responsible for your keys, seeds, limits, and for complying with local law, including tax and AML/KYC. Funds can be lost because of bugs, AI-agent mistakes, prompt injection, or a compromised system. Mainnet only with small amounts, and only after your own review. Authors and contributors are not liable. Full text: DISCLAIMER.md.

Nederlandse samenvatting: experimentele, niet-geauditeerde software; gebruik op eigen risico; geen garantie (MIT); geen financieel, juridisch of fiscaal advies; jij bent verantwoordelijk voor sleutels, seeds, limieten en naleving van lokale wetgeving (belasting, AML/KYC); verlies van funds is mogelijk; mainnet alleen met kleine bedragen na eigen review; auteurs en contributors zijn niet aansprakelijk.

Every coin starts on a test network (Bitcoin: signet). Do not treat a green test suite as a security review.

A small multi-coin hot wallet exposed as an MCP stdio server. An AI agent can pay for things online. By default every payment needs a human approval code. The owner can grant a small per-coin autonomous budget; a payment inside that budget is sent without a code, and anything above it still needs wallet-mcp approve. The agent's spending key is separate from the owner's savings.

The original Bitcoin-only command, btc-wallet-mcp, is still there and behaves as before (signet, BIP84, the same approval code). New deployments should use wallet-mcp.

Vision

The agent earns and pays for services on the owner's behalf. It spends from a hot key with a low per-payment limit and a low daily limit. The owner tops that key up from a cold wallet that never lives on the agent's machine. Amounts above the limit, and any increase of the limit or of the autonomous budget, need a human yes that the agent cannot produce. See ROADMAP.md. The budget is an integer in the coin's base unit. There is no price oracle. The owner dashboard can show an optional EUR estimate; that figure is not used to set or check the budget.

Related MCP server: L402 Gateway

Cold and hot, per coin

Cold

Hot

Where the secret lives

Only on the owner's machine

On the server the agent calls

What the agent server stores

A watch-only xpub or address

An encrypted hot seed, or one master seed shared by the hot coins

Can the server sign?

No. There is no cold send tool, and cold never auto-spends

Yes, after a per-payment approval code, or inside an owner-granted autonomous budget. Always inside the hard limits

What bounds the loss

The cold key is not on this host

The balance the owner deposited, and the daily limit

Top-ups are ordinary transfers the owner sends from their own cold wallet to the hot receive address. This server does not broadcast those top-ups.

A large cold payment can be signed on the owner's machine with a Ledger or Trezor:

wallet-mcp hw-sign --device ledger --file unsigned.json

unsigned.json is a PSBT description (Bitcoin, Litecoin) or an EIP-712 document (Ethereum, Avalanche). The command needs a TTY and the approval secret. It then asks the device to sign. The device must be confirmed physically. There is no MCP tool for this, agent mode is refused, and the server does not broadcast the signature. A hot-wallet approval code does not work here.

Supported coins

Coin

Status

Default network

Also

Library

Base unit

Bitcoin

tested

signet

testnet, testnet4, regtest, mainnet (opt-in)

bdkpython (BDK, BIP84)

sat

Ethereum

tested

sepolia

mainnet (opt-in)

eth-account

wei

Avalanche C-chain

tested

fuji

mainnet (opt-in)

eth-account (chain id 43113 / 43114)

wei

XRP

tested

testnet

devnet, mainnet (opt-in)

xrpl-py

drop

Litecoin

tested

testnet

mainnet (opt-in)

embit (BIP84, LTC bech32)

litoshi

USDC on Ethereum

tested

sepolia

mainnet (opt-in)

eth-account, ERC-20

10⁻⁶ USDC

USDT on Ethereum

tested

sepolia

mainnet (opt-in)

eth-account, ERC-20 (Aave test token on Sepolia)

10⁻⁶ USDT

USDC on Avalanche

tested

fuji

mainnet (opt-in)

eth-account, ERC-20

10⁻⁶ USDC

USDT on Avalanche

tested

fuji

mainnet (opt-in)

eth-account, ERC-20 (set WALLET_MCP_USDT_AVAX_TOKEN on Fuji)

10⁻⁶ USDT

Solana

experimental

devnet

testnet, mainnet (opt-in)

solders (m/44'/501'/0'/0')

lamport

Tested means the offline suite covers address derivation and a mocked send through the central approval gate. XRP destination tags are part of that: known exchange deposit addresses require a tag, and the tag is bound into the approval code. Litecoin covers BIP84 on testnet and mainnet. Experimental means the same mocked path exists, but the adapter is new or incomplete. Opt-in testnet reads (WALLET_MCP_INTEGRATION=1) never broadcast.

No test broadcasts a transaction. Not on mainnet, and not on testnet either.

Avalanche C-chain uses the same derivation as Ethereum (m/44'/60'/0'/0/0), which is what MetaMask uses for C-chain. A single master mnemonic therefore gives Ethereum, Avalanche C-chain, and ERC-20 tokens on those chains the same address. Litecoin uses m/84'/2'/0' on mainnet (BIP84 coin type 2) and m/84'/1'/0' on test networks. Bitcoin uses m/84'/0'/0' on mainnet and coin type 1 on signet, testnet, and regtest.

Coins selecteren

Nieuwe gebruikers zetten eerst aan welke coins de agent mag zien. Alleen aangevinkte coins worden als MCP-tools geladen. Staat een coin uit, dan bestaat eth_prepare_payment niet, ook niet als de code wel in de repository zit.

Interactief (vinkjes in de terminal):

wallet-mcp configure
Select coins. Enter a number to toggle, s to save, q to quit.
  1. [x] Bitcoin (BTC) — signet — tested
  2. [ ] Ethereum (ETH) — sepolia — tested
  3. [ ] Avalanche C-chain (AVAX) — fuji — tested
  4. [ ] XRP (XRP) — testnet — tested
  5. [ ] Litecoin (LTC) — testnet — tested

Zonder menu, bijvoorbeeld alleen Bitcoin en Ethereum:

wallet-mcp configure --enable btc,eth

Of een pagina op je eigen computer (alleen 127.0.0.1, geen andere interface):

wallet-mcp configure --web
# http://127.0.0.1:8765/

De pagina en het menu schrijven config.json. Ze kunnen een limiet verlagen. Een verhoging wordt een verzoek; die gaat pas in nadat jij in een terminal wallet-mcp approve-limit <id> doet. De configure-pagina vraagt nooit om het approval-secret.

Daarna de server herstarten. list_coins toont wat er echt geladen is.

Wallet-beheer

Eigenaar-dashboard op deze computer. Alleen 127.0.0.1, geen andere interface. WALLET_MCP_MODE=agent weigert te starten. De pagina toont geen seed en maakt geen seed aan; init en backup blijven in de terminal.

wallet-mcp dashboard --port 8770
# http://127.0.0.1:8770/

Op Windows, in cmd.exe:

wallet-mcp dashboard --port 8770

Open daarna http://127.0.0.1:8770/ in een browser op dezelfde pc. Gebruik niet het LAN-adres. Op Windows wijst de naam localhost soms naar IPv6; het programma luistert alleen op 127.0.0.1. Staat wallet-mcp niet op PATH, gebruik .\.venv\Scripts\wallet-mcp.exe dashboard --port 8770.

De pagina volgt dezelfde controles als configure --web: Host- en Origin-check, een CSRF-token per proces, security-headers, en geen assets van een CDN. Verlagen van max_per_tx, max_per_day, auto_max_per_tx of auto_max_per_day gaat meteen in. Verhogen loopt via request-limit / approve-limit en request-auto / approve-auto. Je typt het approval-secret in het wachtwoordveld op de pagina. Dat geheim wordt niet opgeslagen, niet gelogd en niet in een antwoord gezet. De vergelijking is de bestaande HMAC.

Een betaling in Wacht op jouw goedkeuring keur je daar goed (zelfde HMAC, daarna de bestaande send_payment) of wijs je af. Het logboek leest audit.log en onderscheidt een autonome betaling van een betaling die jij goedkeurde.

De euroschatting staat uit tot je Toon schatting in euro kiest. De koers komt alleen via https. Mislukt dat, dan zie je –. Limieten en het autonome budget blijven basiseenheden.

Op Windows kun je dit met één klik starten: Wallet-beheer starten met één klik.

Wallet-beheer starten met één klik

Op Windows opent een snelkoppeling het eigenaar-dashboard. Er blijft een klein venster open met de titel Wallet-beheer en de tekst Sluit dit venster om te stoppen. Sluit dat venster, of druk op Ctrl+C, om het dashboard te stoppen. De browser gaat naar http://127.0.0.1:<poort>/.

Dit startprogramma maakt geen seed aan. init en backup doe je in een zichtbare terminal.

Bij de start leest het programma de gebruikersomgeving (HKCU\Environment, waar setx schrijft). Alleen namen die exact WALLET_MCP_<COIN>_ALLOW_MAINNET zijn worden overgenomen, bijvoorbeeld WALLET_MCP_BTC_ALLOW_MAINNET en WALLET_MCP_USDC_ETH_ALLOW_MAINNET. Vensters die al open stonden zien een nieuwe setx niet vanzelf; dit programma wel. Elke andere variabele blijft zoals het proces die al had. Het goedkeuringsgeheim erft mee en wordt niet gelezen, geprint, gelogd of gekopieerd.

Staat WALLET_MCP_MODE op agent, dan stopt het programma. Het dashboard start dan niet. Draait dit dashboard al op poort 8770, dan opent alleen de browser. Is 8770 bezet door een ander programma, dan kiest het de eerstvolgende vrije poort. Er wordt alleen naar 127.0.0.1 geluisterd.

Snelkoppeling maken

De checkout staat standaard in %USERPROFILE%\Btc-wallet-MCP, met de venv in .venv na pip install -e ..

powershell -ExecutionPolicy Bypass -File .\scripts\build_launcher.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\install_shortcut.ps1

build_launcher.ps1 installeert PyInstaller (extra dev in pyproject.toml) en maakt dist\Wallet-beheer.exe. Die exe bundelt de crypto-bibliotheken niet. Hij start de bestaande venv:

%USERPROFILE%\Btc-wallet-MCP\.venv\Scripts\python.exe -m wallet_mcp launch

Het python-pad wordt gezocht in de mappen boven de exe (.venv\Scripts\python.exe) en anders op die standaardplek. Een ander pad zet je in WALLET_BEHEER_PYTHON.

De build kopieert ook dist\Wallet-beheer.cmd. Hetzelfde script staat in scripts\Wallet-beheer.cmd als je geen exe bouwt. install_shortcut.ps1 maakt een snelkoppeling op het bureaublad naar de exe, of naar de .cmd als de exe ontbreekt, met het icoon assets\wallet-beheer.ico.

Zonder snelkoppeling, in cmd.exe:

wallet-beheer
wallet-mcp launch

wallet-beheer is het console-script wallet_mcp.launch:main, zodat het venster zichtbaar blijft.

Tools

For each enabled coin <coin> (btc, eth, avax, xrp, ltc):

Tool

What it does

list_coins / get_config

Which coins are on, networks, limits, whether sending is enabled. No secrets

<coin>_hot_get_address

Hot receive address (the top-up destination)

<coin>_hot_get_balance

Hot balance in the coin's base unit

<coin>_cold_get_address / <coin>_cold_get_balance

Watch-only cold view. There is no cold send

<coin>_prepare_payment(address, amount, memo)

Builds a payment and checks limits. Inside the autonomous budget it broadcasts and returns txid. Above that budget it sends nothing and returns confirm_id

<coin>_send_payment(confirm_id, approval_code)

Signs and broadcasts a prepared payment that was above the autonomous budget. Requires the human code for that exact payment

btc_list_transactions

Bitcoin only

amount is an integer in the base unit (sats, wei, drops, litoshis).

Approval flow

A payment inside the owner's autonomous budget does not use this flow. prepare_payment broadcasts it and the webhook is only a notification (auto_spent). Everything else:

  1. The agent calls <coin>_prepare_payment and gets confirm_id, amount, and fee.

  2. You run, in your own terminal: wallet-mcp approve --coin btc <confirm_id>. It shows coin, address, amount, and fee, asks for yes, and prints a 12-hex code. The code is HMAC-SHA256 of the approval secret and coin|network|hot|confirm_id|address|amount|fee. An XRP destination tag, when present, is appended as |tag:<n>. A code without that suffix does not approve a tagged payment. Known exchange deposit addresses (Kraken, Bitstamp, plus WALLET_MCP_XRP_TAG_REQUIRED) cannot be prepared without a tag.

  3. You give that code to the agent. <coin>_send_payment checks it in constant time, re-checks the limits, then signs. The code matches one payment only. Five wrong codes lock that coin until wallet-mcp unlock --coin <coin>.

The Bitcoin-only CLI btc-wallet-mcp approve still uses the original code format for the original server. Codes are not interchangeable between the two servers.

Install

On a server, scripts/install.sh creates a system user walletmcp (shell nologin), a systemd unit that runs as that user, and /etc/wallet-mcp/approval-secret mode 0400. The agent account is not that user, so it cannot read the secret. ./scripts/install.sh --dry-run prints the layout and does not create anything.

Docker Compose in deploy/ runs the same way: the image user is walletmcp, and the approval secret is a compose secret file mounted only on the wallet service. It is not an environment variable of the image, and it must not be given to the agent container.

python3 -m venv .venv && . .venv/bin/activate
pip install -e '.[test]'
export WALLET_MCP_PASSPHRASE='long random passphrase'   # decrypts hot seeds; not a BIP39 passphrase
export WALLET_MCP_APPROVAL_SECRET='another long secret, not the passphrase'
wallet-mcp configure --enable btc,eth,avax,xrp,ltc,usdc_eth,usdt_eth,usdc_avax,usdt_avax,sol
wallet-mcp init --all --role hot                         # TTY only; one 24-word mnemonic

init --all is one setup for every enabled hot coin. It writes a single encrypted file, $WALLET_MCP_DATA_DIR/master.enc, and each adapter derives its hot key from that mnemonic with an empty BIP39 passphrase (the keystore passphrase only encrypts the file):

Coin

First key

Network

Bitcoin

BIP84 m/84'/0'/0'/0/0

coin type 1 (m/84'/1'/0'/0/0) on signet, testnet, testnet4, and regtest

Litecoin

BIP84 coin type 2, m/84'/2'/0'/0/0

coin type 1 on test networks

Ethereum, Avalanche C-chain, USDC, USDT

m/44'/60'/0'/0/0

same address on every EVM network. Avalanche C-chain matches Ethereum on purpose

XRP

m/44'/144'/0'/0/0

the configured network selects the endpoint, not the key

Solana

m/44'/501'/0'/0'

the configured network selects the cluster, not the key

The words are shown once. You retype 3 of them before the file is saved. --words 12 selects a 12-word mnemonic; the default is 24. backup --all shows those words again, and only in a TTY.

One leak of this mnemonic exposes every hot coin derived from it. Use a separate init --coin <coin> --role hot when you want that isolation. A per-coin hot/seed.enc keeps working and takes precedence over the master. init --all refuses to run if any enabled coin already has a hot seed, unless you pass --skip-existing (those coins stay on their own seed). Enabling a coin later derives it from the same master, until you give it a seed of its own.

init --all --role cold is refused. Cold keys stay on a hardware wallet or the owner machine; create them one coin at a time with --role cold, or sign with hw-sign. See DISCLAIMER.md.

A single coin still works, and still shows its own secret once:

wallet-mcp init --coin btc --role hot                    # TTY only; 12 words, not the master

On Windows, in an interactive cmd.exe window (a pipe is refused). From the virtualenv Scripts directory, or after .\.venv\Scripts\activate.bat:

set WALLET_MCP_PASSPHRASE=long-random-passphrase
.\.venv\Scripts\wallet-mcp.exe configure --enable btc,eth,avax,xrp,ltc,sol
.\.venv\Scripts\wallet-mcp.exe init --all --role hot
.\.venv\Scripts\wallet-mcp.exe backup --all

--words 12 is optional. init --all --role cold is refused here too. If wallet-mcp is already on PATH, the .\.venv\Scripts\wallet-mcp.exe prefix can be dropped.

Cold wallet, on the owner's machine (not the agent host):

# WALLET_MCP_MODE must not be "agent" here
wallet-mcp init --coin btc --role cold
# writes owner-cold/seed.enc and a public cold-export.json

On the agent host:

export WALLET_MCP_MODE=agent          # refuses to create or keep a cold seed
wallet-mcp import-watch --coin btc --file cold-export.json

import-watch rejects mnemonics, xprv/tprv strings, and XRP seeds.

Add to an MCP client (stdio)

Run this process as a different OS user from the agent. The agent's config must not contain WALLET_MCP_APPROVAL_SECRET or WALLET_MCP_PASSPHRASE.

{
  "mcpServers": {
    "wallet": {
      "command": "/path/to/.venv/bin/wallet-mcp",
      "args": ["serve"],
      "env": {
        "WALLET_MCP_MODE": "agent",
        "WALLET_MCP_PASSPHRASE": "…",
        "WALLET_MCP_APPROVAL_SECRET": "… (>=16 chars, NOT known to the agent)",
        "WALLET_MCP_DATA_DIR": "/var/lib/wallet-mcp"
      }
    }
  }
}

Put the approval secret in the server user's environment (or type it into wallet-mcp approve on that account). Do not put it in the agent's prompt, repo, or MCP client config.

Configuration

File: $WALLET_MCP_DATA_DIR/config.json (default ~/.wallet-mcp/config.json). One block per coin: enabled, network, max_per_tx, max_per_day, max_fee_rate, optional allowlist, auto_max_per_tx, auto_max_per_day, and auto_mac when the autonomous budget is on. Unknown coins and unknown networks are rejected. Chain ids are not in the file; they come from the code.

Env

Default

WALLET_MCP_DATA_DIR

~/.wallet-mcp

config, hot seeds, watch files, ledgers

WALLET_MCP_MODE

owner

agent refuses cold-seed creation and refuses to start if a cold seed is present

WALLET_MCP_PASSPHRASE

–

decrypts hot seeds (min 12 chars)

WALLET_MCP_APPROVAL_SECRET

–

without it, sending is disabled

WALLET_MCP_APPROVAL_WEBHOOK_URL

–

optional HTTPS (or localhost HTTP) POST when a payment is waiting for approval, and a notification when one was sent inside the autonomous budget (event is auto_spent). Failure is logged and does not block the payment. The body has no secret and no approval code

WALLET_MCP_<COIN>_ALLOW_MAINNET

–

must be I_UNDERSTAND_REAL_MONEY to enable that coin on mainnet

WALLET_MCP_BTC_ESPLORA_URL

mempool.space for the network

https required on mainnet

WALLET_MCP_ETH_RPC_URL / WALLET_MCP_AVAX_RPC_URL / WALLET_MCP_XRP_RPC_URL / WALLET_MCP_LTC_ESPLORA_URL

public testnet endpoints

https, except localhost

Default hot limits (base units, amount plus fee):

Coin

Per tx

Per day

Fee cap

btc

50 000 sat

100 000 sat

50 sat/vB

eth

0.001 ETH

0.005 ETH

0.001 ETH worst-case fee

avax

0.01 AVAX

0.05 AVAX

0.001 AVAX worst-case fee

xrp

1 XRP

5 XRP

1 000 drops

ltc

50 000 litoshi

100 000 litoshi

50 litoshi/vB

usdc / usdt

10 tokens

50 tokens

parent-chain gas cap (also counted on eth or avax)

sol

0.01 SOL

0.05 SOL

100 000 lamports

A file that raises a ceiling without a valid HMAC is rejected at startup. Lowering a limit does not need the secret. The configure page cannot raise one. The owner dashboard can, after you type the approval secret.

Autonomous budget

Per coin, auto_max_per_tx and auto_max_per_day are integers in that coin's base unit (sat, wei, drop, litoshi, token base unit, lamport). The default is 0 / 0, which turns the feature off. There is no price feed: 10 000 sats stays 10 000 sats if the market moves.

Both numbers must be less than or equal to max_per_tx and max_per_day. A non-zero pair is sealed with auto_mac, an HMAC of the approval secret, the coin, and those two numbers (the same idea as limits_mac). A file whose numbers do not match auto_mac is rejected at startup and the next spend fails closed. The MCP server has no tool that writes these fields. The configure page cannot write them either. The owner dashboard can lower them immediately and can raise them only by typing the approval secret, which goes through approve-auto.

A hot payment whose amount plus fee fits auto_max_per_tx and the remaining auto_max_per_day is reserved on the sealed ledger under the same file lock as a normal send, then broadcast from prepare_payment. No approval code is required. The row is marked auto and still counts toward max_per_day. The audit log records "auto": true. The webhook fires as auto_spent (a notification). Allowlist, fee cap, mainnet opt-in, and address/memo checks still apply. A cold wallet never takes this path. A token payment also has to fit the parent coin's autonomous budget, because the gas is native currency.

Anything above the budget falls back to wallet-mcp approve. Lowering the budget applies immediately and reseals auto_mac. Setting both numbers to 0 turns the feature off and clears the MAC. Raising it is a queued request plus a TTY approval, same shape as approve-limit.

On Windows, in cmd.exe, from the virtualenv Scripts directory (or after .\.venv\Scripts\activate.bat). Amounts below are satoshis. approve-limit and approve-auto must be an interactive window: they refuse a pipe, ask you to type yes, and use WALLET_MCP_APPROVAL_SECRET from the environment or prompt for it. The first command prints the id the second command needs. This example raises the hard daily cap first, because 150000 is above the default 100000 sat day limit:

set WALLET_MCP_APPROVAL_SECRET=the-same-secret-the-server-uses
wallet-mcp request-limit --coin btc --max-per-tx 50000 --max-per-day 150000
wallet-mcp approve-limit lim_THE_ID_PRINTED_ABOVE
wallet-mcp request-auto --coin btc --auto-max-per-tx 20000 --auto-max-per-day 150000
wallet-mcp approve-auto aut_THE_ID_PRINTED_ABOVE

If wallet-mcp is not on PATH, call .\.venv\Scripts\wallet-mcp.exe with the same arguments. Skip the request-limit / approve-limit pair when the autonomous numbers are already within the hard limits. More detail is in docs/auto-budget.md.

Security model

The review notes for an external audit are in SECURITY.md. A code review from 16 October 2026 is written up in docs/security/AUDIT-2026-10-16.md. It is not a substitute for an independent professional audit. Do not treat a green CI run as one.

Assets: cold seeds (owner machine only), hot seeds, the funds on the hot wallets, token balances on those hot keys, the approval secret.

The server process should run as its own OS user (or its own machine or container). The agent user must not be able to read that user's environment (/proc/<pid>/environ), config.json, or seed.enc. The approval secret is never returned by a tool, never written to the config file, and never logged. Logs go to stderr and contain ids and amounts, not secrets. audit.log in the data directory records every prepare, approve, and send attempt the same way (mode 0600). It does not store codes or secrets.

Threat

What holds

What remains

Agent (or prompt injection) tries to pay by itself

Above the autonomous budget, send_payment needs a per-payment HMAC. Inside it, prepare reserves the spend under a file lock before broadcast and the total cannot pass auto_max_per_tx / auto_max_per_day. Five bad codes lock the coin. Addresses and memos cannot contain control characters or bidi marks. Cold wallets never auto-spend

The owner granted a budget larger than they meant. A human who approves a payment they did not read. A failed broadcast still counts against the daily limit and the auto budget

Agent tries to spend the cold wallet

Cold tools are watch-only. execute refuses a non-hot wallet. Agent mode will not boot if a cold seed file is on disk. A Bitcoin WIF or secret descriptor is rejected as a watch-only import. hw-sign is not an MCP tool and refuses agent mode before it talks to a device

Owner copies owner-cold/ onto the agent host and ignores the warning, or confirms a hardware prompt they did not read

Agent raises its own limit or its autonomous budget

Limit increases need approve-limit and limits_mac. Budget increases need request-auto plus approve-auto and auto_mac. The configure UI and the MCP tools cannot complete either increase. The owner dashboard can, only after the owner types the approval secret, and it refuses WALLET_MCP_MODE=agent. A request expires after 24 hours. A tampered auto_mac fails closed

Same OS user as the server, so the agent can read the secret and forge a MAC. Replacing config.json with an older file the owner had approved restores that older budget. A process on the same machine can open the dashboard if the owner started it

Agent calls a coin that was not selected

That adapter is not registered. The tool name does not exist

–

Tampered unsigned payload

The adapter re-checks address, amount, and fee against the approved payment before sign

A bug in a new adapter; keep the "do not override execute" test

Agent rewrites the spend ledger

The ledger and pending state are HMAC-sealed with the approval secret. A bad MAC fails closed

Deleting the ledger file starts a fresh 24-hour window. Someone who can delete it can usually read the secret too

Hostile chain API

Can lie about balances and fees. Cannot redirect a signed payment: outputs are checked locally, and fees above the cap are refused. Chain URLs must be https (http only on localhost), and redirects are refused

Privacy. Run your own node. An https URL on a private network is still allowed for a self-hosted node

Accidental mainnet

Disabled until WALLET_MCP_<COIN>_ALLOW_MAINNET=I_UNDERSTAND_REAL_MONEY. Mainnet chain URLs must be https

–

Stolen disk

scrypt (N=2^17, bounded) + AES-256-GCM. New files bind the ciphertext to the coin and role. The master file is bound to master:hot; a wrong passphrase fails closed

A weak passphrase. Whoever can read the master file and the passphrase can spend every hot coin derived from it. Files written before context binding still use the shared tag

Out of scope: Lightning, multisig, RBF/CPFP, coin control. Hardware signing is owner-side only (Ledger or Trezor); the agent cannot start it, and this server does not broadcast the result.

Bitcoin-only server

btc-wallet-mcp (init, approve, serve, backup) is unchanged: signet by default, BTC_WALLET_ALLOW_MAINNET=I_UNDERSTAND_REAL_MONEY for mainnet, and the original HMAC over confirm_id|address|amount|fee. Its data directory is ~/.btc-wallet-mcp, separate from ~/.wallet-mcp.

Tests

pytest -q                                          # offline; no broadcasts
BTC_WALLET_INTEGRATION=1 pytest -q -m integration  # live signet read of an empty wallet; still no broadcast
WALLET_MCP_INTEGRATION=1 pytest -q -m integration  # XRP testnet fee + Litecoin testnet fee estimate; still no broadcast

Development

Design notes are in spec/. How to add a coin is in CONTRIBUTING.md. The direction of the project is in ROADMAP.md.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables any MCP-compatible LLM client to autonomously hold, spend, earn, and receive cryptocurrency using native MCP tools, no code required.
    3 npm
    80
    ISC
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server that enables AI agents to request human approval before spending money, check approval status, verify signed tokens, and manage API keys.
    7 npm
    MIT