bridge

package
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: 19 Imported by: 0

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

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

func New(cfg Config) (*Bridge, error)

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

func (b *Bridge) ApplySettings(s Settings) error

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

func (b *Bridge) Close() error

Close stops the listener, closes every bot session and the router. Idempotent.

func (*Bridge) HandlePM

func (b *Bridge) HandlePM(peerUID, text string)

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

func (b *Bridge) Handler() http.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

func (b *Bridge) ListenAddr() net.Addr

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

func (b *Bridge) ResolvePayment(id string, approve bool) bool

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) Settings

func (b *Bridge) Settings() Settings

Settings returns the current settings with defaults applied.

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

func (b *Bridge) Start(ctx context.Context) error

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

type Clock interface {
	Now() time.Time
	After(d time.Duration) <-chan time.Time
}

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

type Payer interface {
	Pay(ctx context.Context, payeeUID string, atoms int64) error
}

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 PayerFunc

type PayerFunc func(ctx context.Context, payeeUID string, atoms int64) error

PayerFunc adapts a function to Payer.

func (PayerFunc) Pay

func (f PayerFunc) Pay(ctx context.Context, payeeUID string, atoms int64) error

Pay implements Payer.

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.

type TipWait

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

TipWait is one outstanding payment.

func (*TipWait) Cancel

func (w *TipWait) Cancel()

Cancel unregisters an abandoned waiter (timeout and context paths).

func (*TipWait) Done

func (w *TipWait) Done() <-chan error

Done delivers the terminal result exactly once.

Jump to

Keyboard shortcuts

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