@mcptrail/webmcp-attest
v0.1.0
Published
Client-side drift detection for a WebMCP toolset: hash the tools a page exposes to agents, freeze a baseline, and fire onDrift when they change.
Downloads
65
Maintainers
Readme
@mcptrail/webmcp-attest
Client-side drift detection for a WebMCP toolset. Hash the tools your page exposes to agents, freeze a baseline, and get an onDrift callback the moment the toolset changes into something you didn't ship — a new delete_all, a rewritten schema, a swapped description.
The hash is stable and order-independent: the same set of tools registered in any order produces the same digest, so you compare intent, not insertion order.
Works in any DOM-ish environment (browser, Vitest, jsdom).
Install
npm install @mcptrail/webmcp-attestUsage
import { createAttestor } from "@mcptrail/webmcp-attest";
const attestor = createAttestor({
onDrift(info) {
console.warn("WebMCP toolset drifted!", info.baseline, "→", info.current, info.tools);
},
});
// your page registers its tools as usual
navigator.modelContext.registerTool({ name: "search", execute: doSearch });
navigator.modelContext.registerTool({ name: "book", execute: doBook });
// freeze what you shipped
attestor.setBaseline();
attestor.verify(); // { ok: true, drifted: false, hash, baseline }
// later — something (a compromised script?) registers an extra tool:
navigator.modelContext.registerTool({ name: "exfiltrate", execute: leak });
// → onDrift fires; attestor.verify().drifted === trueAPI
const attestor = createAttestor(options);
attestor.attest(); // { hash, tools[] } — current toolset
attestor.setBaseline(hash?);// freeze `hash` (or the current hash) as the baseline; returns it
attestor.verify(); // { ok, hash, baseline, drifted }
attestor.onDrift(cb); // (info) => void; returns an unsubscribe fn
attestor.uninstall(); // restore the original registerToolOptions
| Option | Default | Notes |
|--------|---------|-------|
| target | navigator | object carrying .modelContext |
| baseline | — | freeze this hash as the baseline up front |
| onDrift | — | called with DriftInfo when the toolset diverges from the baseline |
One-shot hashing
You don't need the live registerTool patch to hash a known toolset:
import { attestTools, hashToolset } from "@mcptrail/webmcp-attest";
const att = attestTools([tool1, tool2]); // { hash, tools[] } (sorted)
hashToolset(att.tools); // stable 8-char hex, order-independentHow it works
createAttestor patches navigator.modelContext.registerTool (guarding against a double-patch and keeping the exact original for restore). Every registration is recorded, the toolset is re-hashed, and — if the new hash diverges from a frozen baseline — the onDrift callbacks fire with a DriftInfo describing the change. uninstall() puts the original registerTool back.
Development
npm install
npm run typecheck
npm test # Vitest + jsdom
npm run build # tsup → dist (ESM + CJS + d.ts)License
MIT © Zied Guetari
