@gvnrdao/dh-mcp
v0.5.4
Published
MCP server for the Diamond Hands protocol — Bitcoin-collateralized UCD lending with human-approved signing (sepolia practice by default; mainnet fully enabled)
Keywords
Readme
@gvnrdao/dh-mcp
MCP (Model Context Protocol) server for the Diamond Hands protocol. It lets an AI agent — through any MCP host (Claude, IDEs, agent runtimes) — operate Bitcoin-collateralized UCD lending on a user's behalf, with a human approving every fund-moving step.
Install
Pick the row that matches your machine — the first two paths carry their own runtime, so there is nothing to install first (no Node, no npm):
| You are on | Install path |
|---|---|
| Claude Desktop (macOS / Windows) | Download the bundle and open it (double-click / drag into Settings → Extensions): https://assets.gvnr.xyz/dh-mcp/v0.5.2/dh-mcp-0.5.2.mcpb — Claude Desktop runs it on its own embedded runtime; network and optional keystore are configured in the extension's settings UI (the password is held by the OS credential store, never written to a config file). |
| Linux — including Claude Desktop builds on Linux — or any other MCP host | One line, no sudo: curl -fsSL https://assets.gvnr.xyz/dh-mcp/install.sh \| sh — installs a self-contained binary (its own Node runtime inside) to ~/.dh-mcp/bin/dh-mcp, verifies its SHA-256, and prints the exact mcpServers snippet. Then wire the host: Claude Code: claude mcp add diamond-hands -- $HOME/.dh-mcp/bin/dh-mcp (new session); Claude Desktop (Linux builds): the snippet in ~/.config/Claude/claude_desktop_config.json, full quit + relaunch; other hosts: the snippet in their MCP config. Pin a version: curl -fsSL https://assets.gvnr.xyz/dh-mcp/install.sh \| DH_VERSION=0.5.2 sh |
| Developers (requires Node ≥ 18 on PATH — this path does NOT work on a bare machine) | Claude Code: claude mcp add diamond-hands -- npx -y @gvnrdao/dh-mcp · any host JSON: { "mcpServers": { "diamond-hands": { "command": "npx", "args": ["-y", "@gvnrdao/dh-mcp"] } } } |
Downloads landing page (which file is for which system, checksums, security notes):
https://assets.gvnr.xyz/dh-mcp/ — all artifacts and SHA256SUMS live under it
(latest/ and v<version>/ — e.g. v0.5.2/).
Local installs serve local MCP hosts only — claude.ai web / mobile / Cowork cannot reach them. A hosted remote connector (paste-a-URL, wallet-only: OAuth 2.1 + Sign-In- with-Ethereum, external-wallet signing, no keystore mode in that build) is IMPLEMENTED but not yet publicly served — it goes live only after its security audit and rollout gates, and will appear here with its URL when it does.
Unsigned-binary honesty: the macOS and Windows binaries are not yet code-signed —
macOS Gatekeeper may quarantine the download (right-click → Open, or
xattr -d com.apple.quarantine <file>) and Windows SmartScreen will warn. Verify the
SHA256SUMS file from the same release if in doubt. On macOS/Windows the .mcpb path
avoids this entirely.
Troubleshooting spawn npx ENOENT: your MCP host searched the system PATH and found
no npx — the machine has no Node.js (or the host, e.g. an Electron app, cannot see
your shell's version manager). That is the npx path's stated precondition, not a bug in
the server. Remedies: use the one-line installer above (the printed snippet uses an
absolute path to a binary that carries its own runtime), or on macOS/Windows Claude
Desktop install the .mcpb.
Zero configuration boots the real protocol on SEPOLIA (the test network — real contracts, test funds) with public endpoints. Everything is overridable by environment variables:
| Variable | Default | Meaning |
|---|---|---|
| DH_ADAPTER | real | real protocol, or mock (offline practice sandbox; needs DH_MOCK_DB + DH_MOCK_LATENCY) |
| DH_NETWORK | sepolia | sepolia (practice) or mainnet (REAL money — reads and writes both work out of the box) |
| ETH_RPC_URL | public node per network | your own RPC if preferred |
| DH_SERVICE_ENDPOINT | per network | Diamond Hands ops service |
| DH_KEYSTORE_PATH + DH_KEYSTORE_PASSWORD | unset | optional: encrypted keystore for automatic signing (both or neither) |
Signing is chosen by conversation (signer_setup — available on the real adapter):
your own wallet (MetaMask browser popup, or WalletConnect for mobile — scannable QR or
paste-code) or a local encrypted keystore. Safe smart-account and delegation modes are
mock-only for now (table below).
What works where
| Network | Reads (public) | Reads (authenticated) | Writes |
|---|---|---|---|
| sepolia (default — the real protocol, practice money) | always, no setup | after a login: keystore env pair, or "connect my wallet" (signer_setup) | signer modes keystore and external (your own wallet). External-mode BTC withdrawal signs the SDK's bare auth hashes blind — same as the web app; the approval page says so loudly |
| mainnet (real money) | always (indexer gaps are reported honestly) | after a login (the broker throttles logins per address) | signer modes keystore and external, same as sepolia — every operation moves REAL funds behind your confirmation + signature; PSM writes refuse (sepolia-only) |
| mock (DH_ADAPTER=mock — offline practice sandbox) | always | always | all modes, including the simulated Safe and delegation planes |
Signer modes safe and safe-module (Safe smart-account borrower, delegation) are
mock-only pending their own deliveries — on the real adapter they refuse with that
true reason. Reads never trigger wallet popups: an authenticated read without a login
refuses legibly, and the user says "connect my wallet" (signer_setup action connect).
Security posture (what this package will never do)
- No secrets ship, ever. The package contains code only; every credential enters on YOUR machine via env vars, a secure local unlock page, or your own wallet. Passwords pasted into chat are refused by design.
- The approval bridge listens on loopback only (127.0.0.1, token-gated).
- Every fund-moving call requires your explicit confirmation AND a real signature; the card you sign is decoded from the exact transaction — never re-formatted.
- Mainnet operations move real funds — they run only behind your explicit confirmation plus a real signature you approve; PSM mainnet writes refuse (sepolia-only).
What it solves
Diamond Hands is otherwise operated by hand. This server adds a conversational access channel: "open me a 12-month position", "mint 500 UCD", "how is my loan doing?" — while keeping every irreversible action behind explicit human confirmation.
For an agent to act safely on a live lending protocol, it must never act on guessed configuration, stale state, or errors it cannot interpret. The server is therefore built around a strict trust boundary (see below) whose contract is: never guess, never misrepresent upstream state, always answer with legible, typed results.
What it supports
Borrowing lifecycle (write tools — human-gated, signer required):
| Tool | Meaning |
|---|---|
| loan_create | Open a position: BTC custody vault + PKP, returns the vault deposit address |
| loan_mint | Mint UCD against BTC collateral |
| loan_pay | Repay UCD debt (partial or full) |
| loan_extend | Renew the position term (pro-rata fee) |
| loan_withdraw | Withdraw BTC from the vault (unrecoverable — strictest gate) |
| loan_recover_stale_spend | Clear a stale BTC withdrawal reservation (permissionless) |
| loan_allowlist_destination | Register a BTC withdrawal destination in the registry (delay-gated on live networks) |
| psm_swap | Swap USDC ↔ UCD through the PSM (quotes via the psm_quote read; mainnet PSM writes always refuse) |
| loan_delegation_setup / loan_delegation_grant | Safe delegation plane — practice (mock) only today; real refuses with that true reason |
Authorization by conversation: signer_setup — the user picks external wallet
(MetaMask popup / WalletConnect QR), local encrypted keystore, or the Safe modes by
TALKING; secrets never travel through chat. guide serves the walkthroughs
(first-setup, connect-wallet, safe, delegation, troubleshooting, …).
Observability (read tools + resources, no signer needed): position status, listings,
event history, pending withdrawals (position_*, position://{id}), protocol health and
terms (protocol_*, protocol://config, protocol://terms), BTC balance and oracle price
(btc_*), and boundary://info — which world the server is talking to (adapter, sources,
signer identity; never secrets), including its capabilities block: network, chain id,
practice-vs-real money, whether reads need a login, whether writes are enabled (and the
true reason when not), and which signer modes this build offers.
Architecture: the protocol boundary
All protocol traffic passes through one module — the ProtocolBoundary port
(anticorruption layer). Tools and resources never touch protocol surfaces directly.
- Every call returns a result envelope: data + data-source identity + explicit discrepancy warnings, or a typed error from a small closed taxonomy. Raw upstream text never reaches the agent.
- Configuration has zero silent defaults: anything missing or invalid is a loud, actionable
CONFIG_MISSINGerror. - If upstream sources disagree or lag, the boundary reports the divergence explicitly instead of presenting one side as truth.
- A single switch (
DH_ADAPTER=mock|real) selects the adapter behind the port.
The real adapter (the zero-config default) speaks to the live protocol: the Diamond Hands SDK, the lit-ops service, and an Ethereum RPC. Public reads need no setup; authenticated reads need a login; writes run on sepolia through the keystore or the user's own wallet (capability table above). The mock adapter is a full protocol simulator: complete position lifecycle (deposit → active → repaid → closed), LTV-checked mints, fee math, event history — held in memory, persisted to a local file DB (survives restarts), with deterministic operation delays and injectable fault scenarios. It enables end-to-end agent development and testing with zero funds at risk, offline.
In one paragraph: tools and resources talk only to the ProtocolBoundary port; behind it
sit the two adapters, a signer layer (signer_setup) with a loopback-only approval
bridge for external wallets, and a result-envelope contract carrying data provenance and
typed errors — never raw upstream text. The full design docs (use cases, sequence
diagrams, data contracts, scenario catalog) live in the repository:
https://github.com/gvnrdao/diamond-hands.
Reliability status
- Behavior is pinned by scenario tests that run through the genuine MCP request path (in-memory client/server pair), covering happy paths and failure classes alike (missing config, uncollateralized operations, source divergence, upstream outages).
- Current stage: real adapter (sepolia AND mainnet, reads and writes) + mock simulator, all behind the same boundary contract and the same scenario suites. External-wallet signing runs on the real adapter, including BTC withdrawal (blind hash signatures, site-parity — the approval card states the contents are not decodable; keystore withdrawal signs locally).
- Reads are unrestricted, with indexer gaps reported honestly.
Security model
These tools move real funds and create on-chain state. Treat them accordingly.
Write tools require explicit human confirmation (audit H-5)
The irreversible, fund-moving tools — loan_create, loan_mint, loan_pay,
loan_extend, loan_withdraw — are gated by a confirmed: true parameter. They refuse
to run until it is set, and it is meant to be set by a human, not by the model:
- The model should surface the exact operation (amounts, destination address, position) to
the user and obtain explicit approval before re-invoking with
confirmed: true. - These tools are annotated
destructiveHint: true; compliant MCP hosts present an approval prompt. - In a fully headless context with no human in the loop, leave
confirmedunset — the tool will refuse rather than move funds silently.
loan_withdraw sends BTC to an external address and is unrecoverable. The destination
address is validated against the configured BTC network — a mismatched-network address is
rejected before signing — but address correctness is still the human's responsibility.
Signer required for writes
All write tools require a configured signer; without one they return a typed
SIGNER_UNAVAILABLE error. On the real adapter the two remedies are the keystore env
pair (DH_KEYSTORE_PATH + DH_KEYSTORE_PASSWORD) or connecting the user's own wallet
(signer_setup, mode external). In mock mode the signed identity is DH_MOCK_BORROWER.
Supply chain (audit H-6)
Dependencies are pinned to exact versions so nothing drifts silently between installs.
Bump deliberately, in lockstep with sibling packages, and run npm ci in CI.
Mainnet moves real funds
Mainnet reads and writes both work out of the box. Every fund-moving operation still
requires the explicit human confirmed: true plus a real signature (wallet approval
card or keystore), and the network switch ceremony states the money reality loudly.
Review your host's approval flow and key management before using mainnet; PSM mainnet
writes refuse (sepolia-only).
For AI assistants
- Read
boundary://infoFIRST. Itscapabilitiesblock is the truth about the session: network, chain id, practice-vs-real money, whether reads need a login, whether writes are enabled (and the true reason when not), which signer modes exist, and whethersigner_setupis registered. Never promise a capability it does not list. - Use the
guide://topics (also served by theguidetool) —guide://indexlists them;getting-started,first-setup, andconnect-walletcarry the onboarding path. Narrate them in the user's language. - Never invent flows. Every refusal names its true reason and next step — relay that, don't improvise around it.
- Claude Code users may copy the
guide://indexcontent into a project skill for persistent context across sessions.
