brmcp

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 5, 2026 License: ISC Imports: 9 Imported by: 0

README

brmcp - Model Context Protocol over Bison Relay

brmcp carries MCP sessions over Bison Relay private messages. A bot operator serves standard MCP tools to KX'd contacts; callers pay per call with Bison Relay tips. MCP is JSON-RPC 2.0 over a pluggable transport, so this is a conforming transport, not a protocol fork: any MCP client or server built on the official SDKs can ride it.

Why Bison Relay instead of HTTPS:

  • A KX'd contact is a mutually authenticated, end-to-end encrypted channel. No API keys, no TLS certificates, no domain. The caller's BR identity is the principal.
  • The server needs zero inbound reachability. It is just another Bison Relay client, workable behind NAT or Tor.
  • Payments are native. Per-call prices settle as Bison Relay tips (the clients exchange and pay Lightning invoices under the hood) with no billing infrastructure and no LN credentials on the server.
  • The relay stores and forwards, so long-running results survive disconnects.

The two ends and the path between them:

agent (any MCP client)
   |  streamable HTTP + bearer token, localhost
client bridge (bridge/, embedded in the user's BR daemon)
   |  envelope parts over BR private messages; tips settle payments
serving harness (server/, around the operator's BR client)
   |  registered tool handlers
the operator's service

Repository layout

  • wire/ - the envelope codec: framing, chunking, reassembly, deadlines. See WIRE.md for the byte-level specification.
  • the root package - the protocol surface both ends share: the MCP go-sdk custom transport over an abstract private message send/receive pair with per-session routing, the envelope predicate, and the payment metadata.
  • server/ - the serving harness: allowlist, rate limiting, paid tools, the prepaid ledger, tip settlement, bisonbotkit lifecycle.
  • bridge/ - the client bridge: per-bot local MCP endpoints mirroring remote tools, the spending policy (caps, approval/autopay), payment settlement through a host-supplied rail. See BRIDGE.md.
  • brmcptest/ - an in-memory PM fabric for testing endpoints without a relay.
  • cmd/brmcp-serve - a runnable example service with a free tool and a paid tool. Copy its shape to build your own service.

Serving tools

brmcp-serve connects to a RUNNING brclient or brclientd through the clientrpc interface (TLS websocket). Enable clientrpc in your client, then:

go build ./cmd/brmcp-serve
./brmcp-serve -datadir ~/.brmcp-serve

The first run creates two files in the data directory:

  • brmcp-serve.conf - the clientrpc connection (bisonbotkit format: rpc URL, certificates, user, password). Point it at your client's clientrpc.

  • brmcp.json - the harness config:

    { "allowed_uids": ["<64-hex caller uid>"], "calls_per_minute": 30 }

The allowlist is default-deny: with no uids listed, every caller is refused.

Registering your own tools is one call each:

server.AddTool(h, &mcp.Tool{Name: "mytool", Description: "..."},
    priceAtoms, func(ctx context.Context, peer string, in Args) (any, error) {
        // peer is the caller's 64-hex Bison Relay uid.
        return result, nil
    })

A zero price makes the tool free. A positive price is advertised to clients in the tool's _meta under brmcp/priceAtoms and enforced before the handler runs.

Calling tools

The caller side is the bridge package: a library engine a Bison Relay daemon embeds to expose a local streamable-HTTP MCP endpoint per bot (/mcp/<bot-uid>) that agents such as Claude Code connect to. The bridge relays the session over Bison Relay and pays for tools by tip under user-configured caps, either unattended or after per-payment approval. brclientd is the reference host; embedding it in another daemon takes a PM sender, a PM feed, and a payment hook - see BRIDGE.md.

Payments

The server keeps an authoritative per-caller balance ledger:

  • Tips from allowed callers credit their balance (milliatoms are floored to atoms).

  • When a paid call lacks balance, the tool returns an isError result whose text content is machine-readable JSON:

    {"error":"payment_required","tool":"fortune","priceAtoms":10000,
     "balanceAtoms":0,"shortfallAtoms":10000,
     "acceptedRails":["tip"]}
    
  • Tipping at least the shortfall funds the balance; the tip itself is Bison Relay's native invoice exchange between the two clients, so the amount arrives exactly and the bot needs no LN credentials. Retry the call after the tip completes.

  • A handler error refunds the call price; the ledger keeps no other refund path.

Latency expectations

Bison Relay is store-and-forward through a relay: a round trip takes seconds, not milliseconds. Session initialization plus a tool call is typically 3-4 round trips; clients should cache the session and the tool list. Outgoing messages carry a deadline (10 minutes by default) so a request delivered to an offline server does not execute after the caller gave up.

License

ISC. See LICENSE.

Documentation

Overview

Package brmcp carries Model Context Protocol sessions over Bison Relay private messages. It binds the MCP go-sdk's Transport contract to an abstract PM send/receive pair, so the same code serves both a bisonbotkit-backed bot process and an embedded Bison Relay client.

This package holds the protocol surface both ends share: the session Router and Conn over the wire envelope codec, the envelope predicate IsEnvelope, and the payment metadata (PriceMetaKey, PricingMetaKey, CallKeyMetaKey, PaymentRequired). The roles build on it:

  • wire holds the byte-level envelope codec (see WIRE.md).
  • server is the serving harness: default-deny authorization, rate limiting, priced tools, the prepaid ledger, and bisonbotkit glue.
  • bridge is the client bridge: local MCP endpoints that mirror remote bots' tools and settle payments under the user's spending policy.
  • brmcptest is an in-memory PM fabric for tests and examples.

Index

Constants

View Source
const CallKeyMetaKey = "brmcp/callKey"

CallKeyMetaKey is the caller-supplied idempotency key in a tools/call _meta. A transport retry of the same logical call reuses the key; the harness executes and charges once and replays the recorded outcome to duplicates, so a lost reply can never double-bill.

View Source
const PriceMetaKey = "brmcp/priceAtoms"

PriceMetaKey is the tool _meta key advertising the per-call price in atoms, visible to clients in tools/list.

View Source
const PricingMetaKey = "brmcp/pricing"

PricingMetaKey marks tools whose price is computed per call from the arguments; the authoritative quote arrives in the payment_required error.

View Source
const SampleEnvelope = `--mcp[v=1,sid=0123456789abcdef,mid=0123456789abcdef,seq=1/1,exp=1783000000]--eyJqc29ucnBjIjoiMi4wIn0=`

SampleEnvelope is a representative v1 wire frame. Hosts that let users define content filters on private messages should refuse any rule that matches it: filtering envelope frames severs MCP sessions at the receive path.

Variables

This section is empty.

Functions

func IsEnvelope

func IsEnvelope(text string) bool

IsEnvelope reports whether a PM body is brmcp traffic. Hosts that also parse chat commands on the same identity use this to keep MCP parts out of their command dispatch.

Types

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is one MCP session carried over Bison Relay PMs. It implements the go-sdk Connection contract.

func (*Conn) AsTransport

func (c *Conn) AsTransport() mcp.Transport

AsTransport adapts the connection to the go-sdk Transport contract for Server.Connect / Client.Connect, which expect to dial themselves.

func (*Conn) Close

func (c *Conn) Close() error

func (*Conn) Peer

func (c *Conn) Peer() string

Peer returns the Bison Relay uid this session talks to.

func (*Conn) Read

func (c *Conn) Read(ctx context.Context) (jsonrpc.Message, error)

func (*Conn) SessionID

func (c *Conn) SessionID() string

func (*Conn) Write

func (c *Conn) Write(ctx context.Context, msg jsonrpc.Message) error

type PMSender

type PMSender interface {
	SendPM(ctx context.Context, peer string, text string) error
}

PMSender delivers one private message body to a Bison Relay peer, identified by its lowercase 64-hex user id.

type PaymentRequired

type PaymentRequired struct {
	Error          string   `json:"error"` // always "payment_required"
	Tool           string   `json:"tool"`
	PriceAtoms     int64    `json:"priceAtoms"`
	BalanceAtoms   int64    `json:"balanceAtoms"`
	ShortfallAtoms int64    `json:"shortfallAtoms"`
	AcceptedRails  []string `json:"acceptedRails"`
}

PaymentRequired is the machine-readable body of the tool error returned when a paid call lacks balance. It is JSON in the result's text content so non-Go clients can parse it without extensions. Settlement is a Bison Relay tip: the payer's client requests an invoice from this bot's client over the relay (RMGetInvoice/RMInvoice) and pays it; the bot only sees the resulting tip credit.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router demuxes envelope parts arriving on the host's single PM stream into per-(peer, sid) MCP connections.

func NewRouter

func NewRouter(cfg RouterConfig) *Router

func (*Router) Close

func (r *Router) Close()

Close tears down every session.

func (*Router) Dial

func (r *Router) Dial(peer string) (*Conn, error)

Dial creates the client end of a fresh session to peer. The caller owns the returned connection and typically passes conn.AsTransport() to an MCP client Connect.

func (*Router) HandlePM

func (r *Router) HandlePM(peer, text string)

HandlePM feeds one inbound private message. Text that is not a valid envelope part is ignored so the DM thread stays usable for human chat.

type RouterConfig

type RouterConfig struct {
	// Sender delivers outgoing parts.
	Sender PMSender
	// Accept, when non-nil, is invoked for each NEW inbound session
	// (server role). A nil Accept drops unknown inbound sessions, which
	// is the client role: only sessions created via Dial exist.
	Accept func(conn *Conn)
	// Allow gates peers before any state is allocated for them. nil
	// allows everyone; servers should install their allowlist here so
	// unknown peers cannot even fill reassembly buffers.
	Allow func(peer string) bool
	// TTL is stamped as the deadline on outgoing messages. Bison Relay
	// stores and forwards, so without a deadline a request could execute
	// long after the caller gave up. Zero selects 10 minutes.
	TTL time.Duration
	// ChunkSize overrides wire.DefaultChunkSize (tests use small values).
	ChunkSize int
	// Assembler bounds reassembly state.
	Assembler wire.AssemblerConfig
	// InboxSize is the per-session queue of decoded inbound messages.
	// A session that overflows it is closed. Zero selects 128.
	InboxSize int
	// IdleTimeout closes sessions with no traffic in either direction for
	// this long. Zero selects 10 minutes in the server role (Accept set)
	// and no expiry in the client role; negative disables expiry.
	IdleTimeout time.Duration
	// MaxSessionsPerPeer bounds how many concurrent inbound sessions one
	// peer can hold open (server role); further session attempts are
	// dropped. Zero selects 8; negative removes the bound. Locally dialed
	// sessions are not bounded.
	MaxSessionsPerPeer int
	// Logf, when non-nil, receives diagnostic lines.
	Logf func(format string, args ...any)
}

RouterConfig wires a Router to its host.

Directories

Path Synopsis
Package bridge is the client bridge: it exposes a local streamable-HTTP MCP endpoint per allowed Bison Relay bot (/mcp/<bot-uid>, bearer gated, disabled by default) that mirrors the remote bot's tools verbatim and relays every call over the relay.
Package bridge is the client bridge: it exposes a local streamable-HTTP MCP endpoint per allowed Bison Relay bot (/mcp/<bot-uid>, bearer gated, disabled by default) that mirrors the remote bot's tools verbatim and relays every call over the relay.
Package brmcptest provides an in-memory private message fabric so brmcp endpoints (routers, harnesses, bridges) can be exercised end to end without a relay.
Package brmcptest provides an in-memory private message fabric so brmcp endpoints (routers, harnesses, bridges) can be exercised end to end without a relay.
cmd
brmcp-serve command
brmcp-serve is an example MCP service offered over Bison Relay DMs.
brmcp-serve is an example MCP service offered over Bison Relay DMs.
Package server is the serving harness: it publishes MCP tools over Bison Relay private messages with default-deny authorization, per-caller rate limiting, and paid tools settled by Bison Relay tips against a prepaid per-caller balance (the built-in Ledger, or any store implementing Billing).
Package server is the serving harness: it publishes MCP tools over Bison Relay private messages with default-deny authorization, per-caller rate limiting, and paid tools settled by Bison Relay tips against a prepaid per-caller balance (the built-in Ledger, or any store implementing Billing).
Package wire frames opaque payloads (JSON-RPC messages in practice) into Bison Relay private messages.
Package wire frames opaque payloads (JSON-RPC messages in practice) into Bison Relay private messages.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL