Documentation
¶
Overview ¶
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. To the agent this is an ordinary MCP server on localhost; it needs no Bison Relay awareness, no wallet, and no keys.
The bridge is also where the user's spending policy lives: a default-deny bot allowlist, per-call and rolling twenty-four-hour caps that bind in both modes (zero means never pay), and approval or autopay settlement of payment_required refusals through a host-supplied Payer. Hosts embed the bridge by wiring a brmcp.PMSender, feeding inbound private messages to HandlePM, and implementing Payer over their payment rail (TipMatcher helps hosts built on Bison Relay's tip notifications). See BRIDGE.md.
Index ¶
- type Bridge
- func (b *Bridge) ApplySettings(s Settings) error
- func (b *Bridge) Close() error
- func (b *Bridge) HandlePM(peerUID, text string)
- func (b *Bridge) Handler() http.Handler
- func (b *Bridge) ListenAddr() net.Addr
- func (b *Bridge) PendingPayments() []PendingPayment
- func (b *Bridge) ResolvePayment(id string, approve bool) bool
- func (b *Bridge) Settings() Settings
- func (b *Bridge) SpendLog() (entries []SpendEntry, todayAtoms int64)
- func (b *Bridge) Start(ctx context.Context) error
- type Clock
- type Config
- type Payer
- type PayerFunc
- type PendingPayment
- type Settings
- type SpendEntry
- type TipMatcher
- type TipWait
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Bridge ¶
type Bridge struct {
// contains filtered or unexported fields
}
Bridge is the client-side brmcp engine: it mirrors allowed bots' tools on local streamable-HTTP MCP endpoints (/mcp/<bot-uid>, bearer gated, disabled by default), relays calls over Bison Relay, and settles payment_required refusals under the user's spending policy.
func New ¶
New validates cfg, loads the persisted settings and spend state, and builds the router. It opens no sockets and sends nothing; call Start.
func (*Bridge) ApplySettings ¶
ApplySettings validates, persists, and hot-applies s: enabling starts the owned listener, disabling stops it, and a token change while enabled restarts it, severing streams authorized under the old token. Bots removed from the allowlist have their live sessions closed. Enabling with an empty token mints a random one, visible via Settings. Validation and persistence failures leave the prior settings active.
func (*Bridge) Close ¶
Close stops the listener, closes every bot session and the router. Idempotent.
func (*Bridge) HandlePM ¶
HandlePM feeds one inbound private message from peer (64-hex uid). Non-envelope text is ignored, so the host can feed every PM it receives.
func (*Bridge) Handler ¶
Handler returns the bridge's HTTP surface: constant-time bearer auth (an empty token never authorizes), the /mcp/<64-hex-uid> path gate (404 for malformed or non-allowlisted uids), and the streamable-HTTP MCP proxy. While the bridge is disabled it answers 404 to everything. Hosts that mount the handler themselves own the listener lifecycle, including severing long-lived streams when the token changes.
func (*Bridge) ListenAddr ¶
ListenAddr reports the owned listener's bound address, or nil when the bridge is not listening (disabled, Handler-only mode, or a bind failure).
func (*Bridge) PendingPayments ¶
func (b *Bridge) PendingPayments() []PendingPayment
PendingPayments lists payments parked for approval, oldest first.
func (*Bridge) ResolvePayment ¶
ResolvePayment delivers the user's decision for a parked payment, reporting false when no such payment is pending. Repeat decisions on the same id are ignored.
func (*Bridge) SpendLog ¶
func (b *Bridge) SpendLog() (entries []SpendEntry, todayAtoms int64)
SpendLog returns a copy of the recorded payments and the rolling twenty-four-hour total the daily cap is enforced against.
func (*Bridge) Start ¶
Start activates the bridge: ctx becomes the base context for bot sessions, and the owned listener binds when ListenAddr is set and the settings enable the bridge. A bind failure is returned and the bridge stays usable; a later ApplySettings retries the bind. Cancelling ctx closes the bridge.
type Clock ¶
Clock abstracts time so cap windows, approval timeouts, and the post-payment poll are testable. A nil Config.Clock selects the system clock.
type Config ¶
type Config struct {
// DataDir holds mcpclient.json (settings) and mcpspend.json (the spend
// log).
DataDir string
// Sender delivers outgoing envelope parts; the host resolves the peer
// uid to a private message.
Sender brmcp.PMSender
// Payer settles payment_required refusals.
Payer Payer
// ListenAddr, when non-empty, makes the bridge own a TCP listener that
// binds only while the settings enable the bridge. Empty means the host
// mounts Handler() itself and owns the listener lifecycle. The bridge
// never chooses a bind address on its own.
ListenAddr string
// Name brands the MCP client identity and the refusal-note prefix.
// Empty selects "brmcp-bridge".
Name string
// Logf, when non-nil, receives diagnostic lines.
Logf func(format string, args ...any)
// Clock overrides the system clock (tests).
Clock Clock
// TTL, ChunkSize, Assembler tune the underlying router (zero selects
// the brmcp defaults).
TTL time.Duration
ChunkSize int
Assembler wire.AssemblerConfig
}
Config wires a Bridge to its host. DataDir, Sender, and Payer are required.
type Payer ¶
Payer settles one payment of atoms to a Bison Relay peer (64-hex uid), blocking until the payment reaches a terminal state or ctx ends. The bridge derives ctx with the settings' payment-wait deadline. Errors are appended verbatim to the tool result as the reason payment was not made, so implementations author the rail-specific wording (e.g. that a Bison Relay tip attempt keeps running in the background and still credits the payee after a deadline). Return nil only when settlement is confirmed.
type PendingPayment ¶
type PendingPayment struct {
ID string `json:"id"`
Bot string `json:"bot"`
Tool string `json:"tool"`
Atoms int64 `json:"atoms"`
Created int64 `json:"created"`
}
PendingPayment is one payment parked for a human decision (approval mode). The queue is in-memory by design: a parked payment is coupled to the live tool call that wants to pay, and a restart fails that call anyway, so restart means fail-safe denial.
type Settings ¶
type Settings struct {
Enabled bool `json:"enabled"`
Token string `json:"token"`
// Mode is "approval" (every payment waits for a human decision) or
// "autopay" (payments under the caps run unattended). Any other value
// is coerced to "approval".
Mode string `json:"mode"`
// Caps are hard ceilings on BOTH modes; zero means never pay.
PerCallCapAtoms int64 `json:"per_call_cap_atoms"`
PerDayCapAtoms int64 `json:"per_day_cap_atoms"`
// AllowedBots is the default-deny list of callable bot uids (64-hex,
// matched case-insensitively).
AllowedBots []string `json:"allowed_bots"`
// ApprovalTimeoutSecs bounds how long a call waits for a decision.
// Nonpositive selects 120.
ApprovalTimeoutSecs int `json:"approval_timeout_secs"`
// TipWaitSecs bounds how long a call waits for the payment to complete
// before giving up. Nonpositive selects 180.
TipWaitSecs int `json:"tip_wait_secs"`
}
Settings is the user's bridge policy, persisted as mcpclient.json in the data dir. The listen address is deliberately not here: it is host startup configuration, not a runtime setting.
type SpendEntry ¶
type SpendEntry struct {
TS int64 `json:"ts"`
Bot string `json:"bot"`
Tool string `json:"tool"`
Rail string `json:"rail"`
Atoms int64 `json:"atoms"`
// contains filtered or unexported fields
}
SpendEntry is one payment, persisted in mcpspend.json. Entries are recorded when a payment launches and removed again only when the rail reports definitive failure; a payment whose outcome is unknown (the wait deadline passed with the attempt still running) stays counted against the daily cap.
type TipMatcher ¶
type TipMatcher struct {
// contains filtered or unexported fields
}
TipMatcher correlates initiated payments with the host's terminal tip-progress events for Payer implementations built on Bison Relay's tip flow. The notification API carries no attempt id, so matching is FIFO per (payee uid, milliatoms), case-insensitive on the uid. Two concurrent identical-amount payments to the same payee may swap resolutions, which is benign: credits land on the payee's shared balance either way, and exactly one payment is recorded per settled tip.
func NewTipMatcher ¶
func NewTipMatcher() *TipMatcher
func (*TipMatcher) Expect ¶
func (m *TipMatcher) Expect(payeeUID string, matoms int64) *TipWait
Expect registers a waiter for a payment of matoms milliatoms to payeeUID.
func (*TipMatcher) Resolve ¶
func (m *TipMatcher) Resolve(payeeUID string, matoms int64, res error) bool
Resolve completes the oldest waiter matching a terminal tip event with res (nil means settled), reporting false when no waiter matches - the event was not ours (chat tips, dashboard tips). Non-terminal events must not be fed.