The digest a tool's approvals are pinned to.
A fingerprint is the lower-case hex SHA-256 of the canonical JSON (object keys sorted recursively by their bytes, no insignificant whitespace) of exactly these fields of the server's wire definition:
name, title, description, inputSchema, outputSchema,
annotations, execution, _meta
A field that is absent, or present with a JSON null, is left out of the
digest (never encoded as null), so a server flipping between the two does
not look like a new definition. Every other key (icons, future fields) is
ignored. Values are hashed exactly as sent: reordering object keys anywhere
gives the same fingerprint, while any change to a value, including
whitespace inside the description, gives a different one.
ConduitMcp.Client.Policy compares it against the fingerprint recorded at
approval (pin_tools), so a server that rewrites a tool after approval (a
"rug pull") falls back to asking.
iex> a = ConduitMcp.Client.Fingerprint.compute(%{"name" => "read", "inputSchema" => %{"type" => "object", "required" => []}})
iex> b = ConduitMcp.Client.Fingerprint.compute(%{"inputSchema" => %{"required" => [], "type" => "object"}, "name" => "read"})
iex> a == b
true
Summary
Functions
The fingerprint of a ConduitMcp.Client.Tool (computed over its raw wire
map) or of a string-keyed wire Tool map.
The wire fields a fingerprint covers, in no particular order.
Types
@type t() :: String.t()
A lower-case hex SHA-256 digest (64 characters).
Functions
@spec compute(ConduitMcp.Client.Tool.t() | map()) :: t()
The fingerprint of a ConduitMcp.Client.Tool (computed over its raw wire
map) or of a string-keyed wire Tool map.
Raises when a hashed field holds a value the stdlib JSON encoder cannot
encode (wire maps decoded from JSON never do).
@spec fields() :: [String.t()]
The wire fields a fingerprint covers, in no particular order.