Skip to content
Developer Preview

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 what the right response is, which differs by SDK, deliberately.

The one idea to take away: the two SDKs surface the same comparison with two postures. The TypeScript SDK treats a schema fingerprint mismatch as advisory — a warning value in your logs, never an error — because the protocol evolves by adding optional things and an older SDK keeps working against a newer host. The Python SDK is strict: its wheel does not bundle a host, you pair an installed muse with an installed SDK yourself, and the strict gate is what makes a wrong pairing loud at connect time — initialize fails with a typed error naming the required host version. Neither posture is yours to re-decide: in TypeScript do not turn the warning into an error, and in Python do not swallow the error and carry on.

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 recipe jobs go 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.

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.

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.

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:

Terminal window
git clone https://github.com/meta-models/muse-code-sdk
cd muse-code-sdk
npm ci && npm run build --workspace @muse-code/sdk
MUSE_BIN=$(command -v muse) \
npm run recipes --workspace @muse-code/sdk-cookbook -- --only fingerprint-mismatch

The runnable TypeScript 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 Python twin (same recipe id, same segments, the strict fork) runs in its own CI lane against the same release-built host.

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.