Releases: briandconnelly/moonbridge
Releases · briandconnelly/moonbridge
Release list
v0.3.0
Upgrading is mandatory for anyone on 0.2.0: that release cannot import (see the first entry under
Fixed). Four changes are marked Breaking below. Three of them are behavioral, so no schema diff
surfaces them — read those before upgrading; the fourth (meta.roots_source) does show up in a
diff, as an added enum value. FINGERPRINT moves schema-5 -> schema-6; RESULT_FORMAT moves
8 -> 9.
Added
kimi-code 0.39.1toSUPPORTED_VERSIONS, after re-running the full probe set from
docs/UPGRADING-KIMI.mdagainst the binary (captures and results indocs/kimi-help/0.39.1/,
M0-FINDINGS.md). No guarantee moved — the read-only--agent-fileallowlist, the
confidentiality limit, the silent-effort-ignore, the worktree's non-containment, the argv
ceiling, and thestream-jsonline shapes all hold.protocol_eras_servedon the capabilities surface: every protocol revision this server
negotiates, read from the installed SDK.protocol_revisionalone names only the newest
revision served, so it can no longer tell a client whether the handshake era is reachable.unsupportedas aRootsSourcevalue (see the roots removal under Changed). It was added
rather than replacingclient/not_negotiated/probe_failed, so a stored envelope still
validates against the published value set.- A
protocol_erassection in the manifest, with a matchingFINGERPRINT_COVERStoken,
capturing each era's negotiated revision, advertised capabilities, and instructions from live
connections. Capabilities are covered per era because the two genuinely differ (listChanged
is true on the handshake era, false on the modern one).
Changed
- FastMCP 3.4 -> 4.0, MCP SDK 1.29 -> 2.1 (ADR 0005, superseding ADR 0004, whose own revisit
trigger this was). The server now serves both protocol eras — the 2025-11-25 handshake and the
sessionless 2026-07-28 — because FastMCP 4 negotiates per connection. Agent-visible
consequences:- Breaking: a call that omits
workspace_rootnow resolves somewhere else. MCP SDK v2
removedctx.list_roots()on every era (verified, not inferred:hasattr(Context, "list_roots")is False under bothmode="legacy"andmode="auto"), so no roots probe runs
and the client's roots can no longer supply a workspace. Such a call falls back to the
server's own launch directory withmeta.workspace_warning. A caller that relied on MCP roots
to aim work at a repository will silently target the wrong directory — pass
workspace_root. This is a behavior change, not a schema change, so no schema guard catches
it for you. - Breaking:
meta.roots_sourceis now alwaysunsupported, a value no existing client has
seen. Code branching onclient/not_negotiated/probe_failedfalls through every arm. - Breaking: an explicit
workspace_rootoutside the client's advertised roots used to be
refused at zero spend (workspace_outside_roots, carryingcandidate_roots). With no
roots to compare against, that refusal can no longer fire, so the same input now runs and
spends. If you relied on it as an aim-check, you have lost it — validate the path yourself. - Breaking: resource-read not-found is era-dependent.
-32002on the handshake era, but
-32602on 2026-07-28, which forbids the old code — and a default FastMCP 4 client negotiates
that era, so a client matching the numeric-32002stops matching. Branch on the symbolic
error.data.code("resource_not_found") instead.resource_error_carrierdocuments the
split. workspace_source == "roots", theworkspace_outside_rootserror code, and its
candidate_rootsdetail are still advertised but unreachable, for the same reason. Retiring
them would be breaking and is deliberately deferred.protocol_revisionmoves2025-11-25->2026-07-28, the newest revision served.- The advertised
initializecapabilities lostexperimental: {}— a field removal, and so
breaking by the letter of the rules, though it announced a capability this server never
implemented and no client could usefully have branched on. - Tool and capability descriptions no longer tell agents their MCP roots can select the working
directory; they now say roots are unavailable and thatworkspace_rootis what aims a call. - The
pydanticfloor moves>=2->>=2.12, which MCP SDK v2 requires. A project pinning an
older pydantic gets an unsatisfiable resolution rather than a silent downgrade.
- Breaking: a call that omits
pontonier0.5.0 -> 0.7.0.CONTRACT_API_VERSIONstays 1. The one change that reaches an
agent:redaction.exc_summarynow strips UnicodeCccode points before redacting, so
exception text carrying a control character produces different message text where this bridge
feedsexc_summaryintoerror.message. That is human-readable prose only, so it moves no
fingerprint and is not breaking. It is a hardening — a control character wedged into a secret
previously defeated the redactor's patterns outright.FINGERPRINTmovesschema-5->schema-6andRESULT_FORMATmoves8->9. Every
schema-level change above is itself additive or a value change to a field whose documented
meaning is unchanged; the release is flagged breaking for the behavioral changes marked above.
RESULT_FORMATmoves because a format-8 reader's closedRootsSourceLiteral rejects a record
carryingunsupported, which this release stamps on every record — without the bump such a
record would be misreported as corruption rather than a cross-release payload.
Fixed
- Every 0.2.0 install is broken; only upgrading fixes it.
pyproject.tomldeclared
fastmcp>=3.4with no upper bound, and.mcp.jsoninstalls this plugin with
uvx --from git+<remote>@v<version>, which resolves dependencies fresh and does not read
uv.lock. When fastmcp 4.0.0 published, every user's install picked it up and the server failed
at import —ImportError: cannot import name 'McpError' from 'mcp'— while CI stayed green
against the locked fastmcp 3.4.7. The dependency is nowfastmcp>=4,<5; the upper bound turns
the next major from a production break into a resolution failure at upgrade time. Because
.mcp.jsonpins a git tag, the fix reaches users only when they move to this release's tag. - The
initializecapability seam readextensionsoffmodel_extra, where MCP SDK v2 makes it
a declared field. The lookup found nothing and nulled the whole key, which would have suppressed
every extension the server legitimately advertises rather than only the unimplemented
io.modelcontextprotocol/uione. classify_failurenow tests failure signatures in pontonier's shared precedence order
(drift -> auth -> rate limit -> invalid model), which the library documents as the part every
bridge must agree on. It previously testedinvalid_modelfirst andauthahead of drift, so a
run whose output carried both a contract-drift message and an invalid-model or auth message
classified asinvalid_model/kimi_auth_requiredhere andcli_contract_changedunder the
shared classifier — masking the one signal that must stay loud. Verified against the 0.35.0 and
0.39.1 captures: no captured kimi message changes classification. No error code, field, or
documented meaning changes.- An unresolvable
default_modelinconfig.tomlis now diagnosed as itself. kimi emits
model X does not resolve to a configured providerwhendefault_modelnames an alias with no
[models."..."]section, failing while it resolves the default and so reaching the classifier
even on a run that passed a valid--model. That message previously matched no signature and
surfaced as a generic error, pointing operators at an upgrade bug rather than at their own
config; it now classifies asinvalid_model. The repair hint for this cause namesconfig.toml
and dropsdetails.field = "model"— the caller-supplied-modelcase is unchanged, but
advising the configured-default case to omitmodeland fall back ondefault_modelsteered
callers into a repair loop, sincedefault_modelis the broken thing.
Install
Not on PyPI — install from the pinned tag:
The --from git+… is required. An unrelated package also called moonbridge exists on PyPI, so
plain uvx moonbridge-mcp or pip install moonbridge will not get you this server.
Full changelog: v0.2.0...v0.3.0