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).
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 test in crates/e2e exercises the
full path — it starts a local Nostr relay (nak serve), a regtest bitcoind
(managed by corepc-node), runs the
real server against that node, then runs the real client and checks it receives
live regtest data.
That test needs 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.