Releases: alice-jin-dev/bodybridge
Releases · alice-jin-dev/bodybridge
Release list
bodybridge v1.1.0
bodybridge v1.1.0
First feature release after v1.0.0. Adds the declaration hook to the bridge core and fixes a payload-serialization error-reporting bug. Existing adapters need zero changes.
Added
- Declaration hook — adapters can now declare per-action MCP tools with typed/constrained JSON Schema (e.g. a
move_headtool with an enforced angle range). The bridge mechanically translates each declaration into a FastMCP tool plus a centralized bridge-side validator; the bridge core carries zero device-specific semantics — all constraints are decided by the adapter's type annotations, not by the bridge. This is an optional, lazy mechanism: adapters that don't calldeclare_tools()behave exactly as in v1.0.
Fixed
- A payload the bridge cannot serialize no longer reports
offline. When building the outbound frame fails (deeply nested values →RecursionError, non-JSON values →TypeError, circular references →ValueError), the bridge previously caught it together with send failures and reportedofflinewithretryable: true— while the device was in fact connected. It now returnsbad_paramswithretryable: falseand a plain-language reason. Genuine send failures still reportoffline. The success path is unchanged.
Migration
- Zero changes required for existing adapters. The declaration hook is optional and lazy — an adapter that doesn't call
declare_tools()runs identically to v1.0. No config changes, no new environment variables.
Full changelog: see CHANGELOG.md
bodybridge v1.0.0
First release. No version was published before this one, so nothing is missing
from this file — the Changed, Removed and Fixed entries below do not
refer to an earlier release. They describe how 1.0.0 differs from the untagged
development-period commits that some self-hosted deployments are already
running from git. If yours is one of those, MIGRATION.md is the
step-by-step version of the same information.
Added
- MCP tools.
ping,device_list_capabilities,device_get_statusand
device_send_command, served over streamable-http. - Uniform result envelope. Every device tool returns the same five keys —
ok,message,data,error,retryable— on success and on failure
alike, so a caller never has to branch on shape. - Error codes.
offline,busy,timeout,internal_error,
unknown_command,bad_params. A device-level failure comes back as
ok: false, not as an MCP protocol error. - OAuth 2.1 authorization. Authorization-code flow with PKCE, a
password-gated consent page at/oauth/authorize, a token endpoint at
/oauth/token, and RFC 9728 / RFC 8414 discovery metadata. Access tokens
are self-contained JWTs; the bridge keeps no session, code or token table. - Client registration, two modes. Dynamic Client Registration (RFC 7591)
is the default and makes no outbound request. Client ID Metadata Documents
(CIMD) can be selected withBODYBRIDGE_CLIENT_REGISTRATION=cimd. - Device endpoint. A
/deviceWebSocket endpoint that a device connects
to, guarded by three refusal gates (adapter capability, device token
configured, Bearer match). Refusals are logged server-side. - Generic device protocol. Versioned JSON frames (
v: 1) over WebSocket.
Any device that can open a WebSocket speaks it — no adapter to write and
nothing to install on the device side. - Single-connection ownership. A new device connection replaces the old
one, and the displaced connection's later teardown cannot clear the new one. - ESP32 reference firmware under
firmware/, verifying the bridge's
certificate against embedded ISRG roots. - Configuration.
BODYBRIDGE_TOKEN,BODYBRIDGE_PASSWORDand
BODYBRIDGE_PUBLIC_URLare required. Optional:BODYBRIDGE_HOST,
BODYBRIDGE_PORT,BODYBRIDGE_CLIENT_REGISTRATION,
BODYBRIDGE_CIMD_ALLOWLIST,BODYBRIDGE_TOKEN_TTL_DAYS,
BODYBRIDGE_COMMAND_TIMEOUT_SECONDS,BODYBRIDGE_DEVICE_TOKEN,
BODYBRIDGE_HEARTBEAT_SECONDS,BODYBRIDGE_MAX_PAYLOAD_BYTES,
BODYBRIDGE_MAX_INFLIGHT. A platform-injectedPORTtakes precedence over
BODYBRIDGE_PORT. - Bridge-side deadline. A command that gets no device reply within
BODYBRIDGE_COMMAND_TIMEOUT_SECONDS(default 25) returnstimeoutrather
than hanging. The message says the command may or may not have run, because
that is what the bridge actually knows. - Back-pressure. More than
BODYBRIDGE_MAX_INFLIGHTcommands awaiting a
device result returns a retryablebusy. - Heartbeat. The bridge pings the device every
BODYBRIDGE_HEARTBEAT_SECONDS; a missed pong closes the connection and the
device is marked offline immediately. - Documentation. Configuration reference,
Connecting a device,
Deployment, MIGRATION.md and
SECURITY.md.
Changed
BODYBRIDGE_TOKENmeans something different. It is now the server's own
JWT signing secret, never handed to a client. It is no longer a shared
password that clients present.BODYBRIDGE_HOSTdefaults to0.0.0.0(was127.0.0.1). The bridge is
meant to be reachable from outside, and it refuses to start without
authentication configured. Set127.0.0.1explicitly for local-only access.- The platform's
PORTwins. Precedence isPORT>BODYBRIDGE_PORT>
8000. SettingPORTin a local.envsilently overrides
BODYBRIDGE_PORT. BODYBRIDGE_PUBLIC_URLis required. Previously it fell back to
http://127.0.0.1:8000with a warning; the bridge now exits 1 when it is
unset. Every OAuth metadata field and theiss/audof every issued token
derive from it, so on a public deployment the old fallback made all of them
wrong at once, silently. A value that is set but malformed still falls
back with a warning — only missing is fatal.- The default adapter drives a real device.
WebSocketAdapterreplaces
MockAdapter, so the three device tools report real connection state
instead of fake data. With no device connected they returnoffline—
includinglist_capabilities, which asks the device rather than serving a
static list.adapters/mock.pyremains in the tree and can be selected by
editing one line inserver.py. internal_erroris no longerretryable. An unexpected internal fault
is not something a caller should retry into.ESP32Adapteris nowWebSocketAdapter, andadapters/esp32.pyis now
adapters/websocket.py. Behaviour is unchanged. The class never held
ESP32-specific logic; ESP32 remains the reference firmware. This only
affects forks that import the class directly..envis loaded automatically at startup, so no--env-fileflag is
needed for a local run.
Removed
- Static-token authentication. Presenting the raw
BODYBRIDGE_TOKENas a
Bearer credential is rejected. Clients obtain a JWT through the OAuth flow.
Fixed
- OAuth parameters are read from the query string on an
/oauth/authorize
POST, not only from the body. - The local fallback base URL uses a loopback address instead of
0.0.0.0,
which is a bind target and not a reachable address.
Security
- Authentication is mandatory. The bridge exits 1 rather than start
withoutBODYBRIDGE_TOKENorBODYBRIDGE_PASSWORD, instead of starting up
open or with OAuth silently broken. - Every request is verified. Transport is stateless, so each MCP call
carries and re-verifies its own token — signature,exp,audandiss
are all checked explicitly. Authorization is not granted once at handshake
time. - Audience binding. An issued token is bound to this bridge's own
resource; a token request naming a different resource is rejected with
RFC 8707invalid_target. - Only one signing algorithm. HS256 is specified explicitly when signing
and when verifying, so a token cannot select its own algorithm. - SSRF hardening on CIMD fetches. Private, loopback, link-local and
reserved addresses are refused; DNS is pinned between resolution and
connection; the response body is size-capped.BODYBRIDGE_CIMD_ALLOWLIST
narrows it further to named hosts. Under the defaultdcrmode no outbound
fetch happens at all. - The device endpoint is closed by default. Without
BODYBRIDGE_DEVICE_TOKENthe/deviceendpoint admits nobody; the bridge
starts anyway and says so. Refused handshakes are logged server-side. - Frame-size guard. Device frames larger than
BODYBRIDGE_MAX_PAYLOAD_BYTES(default 64 KB) close the connection with
code 1009. - Firmware verifies TLS. The ESP32 reference firmware checks the bridge's
certificate chain against embedded ISRG roots. - Authorization responses carry
iss(RFC 9207), on error responses as
well as successful ones. - Consent page hardening.
X-Frame-Options: DENYandCache-Control: no-storeon the authorization and registration responses. - Documented non-goals. SECURITY.md states what this
version deliberately does not defend against, rather than implying complete
coverage.