ContextBTC Rust provides a Model Context Protocol (MCP) interface to a Bitcoin Core node, using ContextVM to transport MCP messages over Nostr. Nostr's cryptographic keypairs and signed events provide built-in verification and authorization.
The server needs a stable Nostr identity. Generate a secret key with nak (included in the dev shell):
nak key generate
# -> 7b94e287...bc6148d (64-char hex secret key)Derive the public key (what clients target) from a secret key with:
nak key public <secret-key-hex>The server is configured via environment variables. For local development, copy the provided template and fill in your values:
cp .env.example .env
# edit .envOn startup the server automatically loads a .env file if present. Real
environment variables always take precedence over .env, and a missing file is
not an error (useful for systemd/Docker where variables are injected directly).
.env is gitignored, so your secrets are never committed.
| Variable | Required | Default | Description |
|---|---|---|---|
SERVER_NOSTR_SECRET_KEY |
No | ephemeral | 64-char hex or nsec... key. If unset, a temporary key is generated on each start (testing only, not for production). |
NOSTR_RELAY_URLS |
No | ws://localhost:10547 |
Comma-separated relay websocket URLs, used by both server and client. |
BITCOIN_RPC_URL |
No | http://127.0.0.1:8332 |
Bitcoin Core JSON-RPC endpoint. |
BITCOIN_RPC_USER |
Yes | — | JSON-RPC username. |
BITCOIN_RPC_PASSWORD |
Yes | — | JSON-RPC password. |
BITCOIN_RPC_TIMEOUT_SECS |
No | 30 |
Overall HTTP request timeout for RPC calls, in seconds. |
This is a Cargo workspace with two binary crates:
crates/server— the ContextBTC MCP server (contextbtc-server).crates/client— an example client (contextbtc-client).
Alongside them sit two library crates that are not workspace members. They are
pulled in through [patch.crates-io] in the root Cargo.toml, which swaps them
in wherever a dependency asks for bitcoincore-rpc:
crates/bitcoincore-rpc-client— publishes the crate namebitcoincore-rpcand keeps the upstreamRpcApisurface, but sends each call as an MCPtools/callover Nostr instead of HTTP JSON-RPC.crates/bitcoincore-rpc-json— the matchingbitcoincore-rpc-jsontypes.
The point of the patch is that unmodified crates.io libraries built on
bitcoincore-rpc — bdk_bitcoind_rpc, say — end up talking to a ContextBTC
server without any awareness of the transport. They stay out of members so
cargo fmt/cargo clippy --workspace don't lint vendored upstream code.
With a .env file in place:
cargo run -p contextbtc-serverAlternatively, set variables inline (these override any .env values):
SERVER_NOSTR_SECRET_KEY=<secret-key-hex> \
BITCOIN_RPC_URL=http://127.0.0.1:18443 \
BITCOIN_RPC_USER=myuser \
BITCOIN_RPC_PASSWORD=mypass \
cargo run -p contextbtc-serverCLIENT_NOSTR_SECRET_KEY=cargo run -p contextbtc-client -- <server-pub-key-hex>cargo test --workspaceMost tests are pure unit tests. The end-to-end tests in crates/e2e exercise the
full path. Both start the same stack (crates/e2e/tests/harness/): a local Nostr
relay (nak serve), a regtest bitcoind (managed by
corepc-node), and the real server
against that node.
tests/e2e.rsruns the real client and checks it receives live regtest data.tests/filter_iter.rssyncs a descriptor with BDK's compact block filter (BIP157/158)FilterIter, over the patchedbitcoincore-rpc— so the whole sync travels over MCP/Nostr. It mines a block paying a descriptor address and asserts that block was matched by its filter and its output reached the graph. Needsbitcoindstarted with-blockfilterindex=1, which the harness does.
Those tests need bitcoind and nak available. The dev shell provides both, so
the simplest way to run the whole suite is:
nix develop --command cargo test --workspaceOutside the dev shell, make nak available on PATH and point corepc-node at a
bitcoind binary via BITCOIND_EXE. The e2e test fails if either is missing —
it never silently skips — so cargo test needs both present. This is the same
command CI runs (see .github/workflows/ci.yml).
Running e2e tests only with logs:
cargo test -p contextbtc-e2e -- --nocaptureThe flake exposes prebuilt packages, so any machine with Nix (flakes enabled) can run the server or client straight from GitHub — no clone, no toolchain setup:
# Run the server
nix run github:karliatto/contextbtc
# Run the client (note the `--` before program arguments)
nix run github:karliatto/contextbtc#client -- <server-pub-key-hex>Configuration works the same way as a local run: pass the environment variables from the Configuration table inline, e.g.
SERVER_NOSTR_SECRET_KEY=<secret-key-hex> \
NOSTR_RELAY_URLS=wss://relay.contextvm.org \
BITCOIN_RPC_URL=http://127.0.0.1:8332 \
BITCOIN_RPC_USER=myuser \
BITCOIN_RPC_PASSWORD=mypass \
nix run github:karliatto/contextbtcTo build without running, or to install into your profile:
nix build github:karliatto/contextbtc # -> ./result/bin/{contextbtc-server,contextbtc-client}
nix profile install github:karliatto/contextbtcFor a NixOS host, the flake also provides a module (nixosModules.default) that
runs the server as a hardened systemd service. Add it to the target machine's
flake:
{
inputs.contextbtc.url = "github:karliatto/contextbtc";
outputs = { nixpkgs, contextbtc, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
contextbtc.nixosModules.default
{
services.contextbtc = {
enable = true;
relayUrls = [ "wss://relay.contextvm.org" ];
# Non-secret settings:
extraEnvironment.BITCOIN_RPC_URL = "http://127.0.0.1:8332";
# Secrets (SERVER_NOSTR_SECRET_KEY, BITCOIN_RPC_USER/PASSWORD, ...)
# live in a file read at runtime, never in the Nix store:
environmentFile = "/run/secrets/contextbtc.env";
};
}
];
};
};
}Then sudo nixos-rebuild switch. The service runs as an isolated DynamicUser
with automatic restart.
This project bridges two distinct protocol layers:
- Client ⟷ ContexVM MCP server: MCP over Nostr.
- ContexVM MCP server ⟷ bitcoind: JSON-RPC over HTTP.
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
crates/bitcoincore-rpc-client and crates/bitcoincore-rpc-json are vendored
from rust-bitcoincore-rpc
v0.19.0 and remain under its original CC0-1.0 dedication. Their per-file
headers are kept as-is.