Handle a fingerprint mismatch
One day your app will connect to a host that is newer than the SDK you built with. This page shows you how to notice that at the handshake — and why the right response is a warning in your logs, not an error in your user’s face.
The one idea to take away: a schema fingerprint mismatch is advisory. The protocol evolves by adding optional things, so an older SDK keeps working against a newer host. The SDK already follows this posture: it compares fingerprints at the handshake and hands you a warning value instead of throwing. Your only job is to not turn that warning back into an error.
Where the fingerprint comes from
Section titled “Where the fingerprint comes from”Every connection opens the same way — your client sends initialize:
{"dir":"client","raw":"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"clientInfo\":{\"name\":\"conformance\",\"version\":\"0.0.0\"}}}"}The host’s reply describes itself, and one member of that reply is the
schema object: its version is the served protocol-schema version and its
fingerprint is the value this whole page turns on.
This page shows you no copy of that reply — a copy is exactly the kind of
content that drifts. The tested recipe reads the real thing instead: it takes
schema and schema.fingerprint from a live release-built host’s
initialize result, by those member names, so if either member ever moves or
is renamed, the cookbook-journeys job goes red — this page cannot quietly
disagree with the wire.
The SDK pins the fingerprint it was written against as
EXPECTED_SCHEMA_FINGERPRINT and compares the served value against that pin
during initialize.
The code
Section titled “The code”After the handshake, check one field:
const msp = await handshake.initialize({ clientInfo: { name: "my-app", version: "1.0.0" },});
if (msp.fingerprintWarning !== undefined) { // The host serves a schema this SDK was not built against. Surface it // where a developer will see it — then carry on. Everything your SDK // version knows how to do still works. console.warn(msp.fingerprintWarning.message);}That is the whole recipe. The warning value tells you both sides of the
comparison (pinned is what your SDK expected, served is what the host
advertised) and carries a message written for a human reading logs.
If you want the same comparison outside a live connection — say, in a diagnostic screen — call it directly:
import { EXPECTED_SCHEMA_FINGERPRINT, checkServedFingerprint } from "@muse-code/sdk";
const warning = checkServedFingerprint(servedFingerprint);// undefined on a match; a descriptive warning value on a mismatch.// It never throws.What not to do
Section titled “What not to do”Do not refuse to run when the fingerprints differ. The three-line mistake looks responsible and is not:
// WRONG: this turns every host upgrade into an outage for your users.if (msp.fingerprintWarning !== undefined) { throw new Error("schema mismatch");}A mismatch means the host moved ahead of your SDK, which is the normal state of the world between your releases. Log it, surface it in diagnostics, and plan an SDK update — while your app keeps working.
Run it yourself
Section titled “Run it yourself”You need Node 20 or newer, an installed muse binary, and the SDK your own
application depends on — npm install @muse-code/sdk. The recipe programs live
in the SDK repository:
git clone https://github.com/meta-models/muse-code-sdkcd muse-code-sdknpm ci && npm run build --workspace @muse-code/sdkMUSE_BIN=$(command -v muse) \ npm run recipes --workspace @muse-code/sdk-cookbook -- --only fingerprint-mismatchWhere the tested code lives
Section titled “Where the tested code lives”The runnable source for this page is published in full, comments and all, as the fingerprint-mismatch example. It is the source that runs, not a retelling of it: it is executed end to end on every change to this area, and every docs build checks the wire exchange above against the committed transcript, so this page cannot drift from what actually runs.
The harness spawns its hosts with MUSE_EXPERIMENTAL_SDK_ENABLED set
explicitly. muse serve now runs by default; the variable survives as an
off-switch, and the harness pins the run open so an operator’s off in the
environment cannot silently change what this page proves. That is not advice
for your application — build against muse serve as a supported command.