# `ConduitMcp.Client.Fingerprint`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/client/fingerprint.ex#L1)

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

# `t`

```elixir
@type t() :: String.t()
```

A lower-case hex SHA-256 digest (64 characters).

# `compute`

```elixir
@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).

# `fields`

```elixir
@spec fields() :: [String.t()]
```

The wire fields a fingerprint covers, in no particular order.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
