---
title: "Agents"
description: "The same hash tools over MCP and Pi and OMP and the AI SDK. What they take and what they refuse and what the model reads back"
canonical_url: "https://hashes.agntn.dev/guide/agents"
---
# Agents

> The same hash tools over MCP and Pi and OMP and the AI SDK. What they take and what they refuse and what the model reads back

## Same tools, every host

The MCP server, the Pi and OMP extensions and the AI SDK tools in `@agntn/hashes/ai` all call the executors in `src/tool-operations.ts`. Same arguments, same checks, same text back, and a fix lands once. This site runs those executors too, so the `Full tool response` dialogs on the landing and in the [playground](https://hashes.agntn.dev/playground) are exactly what a model reads.

<table>
<thead>
  <tr>
    <th>
      Tool
    </th>
    
    <th>
      Does
    </th>
    
    <th>
      Arguments
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        hashes_compute
      </code>
    </td>
    
    <td>
      Digest of text or bytes
    </td>
    
    <td>
      <code>
        algorithm
      </code>
      
      , <code>
        input
      </code>
      
      , <code>
        inputEncoding
      </code>
      
      , <code>
        encoding
      </code>
      
      , <code>
        salt
      </code>
      
      , <code>
        parameters
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hashes_hmac_compute
      </code>
    </td>
    
    <td>
      HMAC with a key
    </td>
    
    <td>
      <code>
        algorithm
      </code>
      
      , <code>
        input
      </code>
      
      , <code>
        inputEncoding
      </code>
      
      , <code>
        key
      </code>
      
      , <code>
        keyEncoding
      </code>
      
      , <code>
        encoding
      </code>
      
      , <code>
        parameters
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hashes_verify
      </code>
    </td>
    
    <td>
      Compare with an expected digest
    </td>
    
    <td>
      <code>
        algorithm
      </code>
      
      , <code>
        input
      </code>
      
      , <code>
        inputEncoding
      </code>
      
      , <code>
        expected
      </code>
      
      , <code>
        encoding
      </code>
      
      , <code>
        salt
      </code>
      
      , <code>
        parameters
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hashes_digest_extend
      </code>
    </td>
    
    <td>
      <a href="https://hashes.agntn.dev/guide/extend">
        Length extension
      </a>
      
       of a known digest
    </td>
    
    <td>
      <code>
        algorithm
      </code>
      
      , <code>
        digest
      </code>
      
      , <code>
        message
      </code>
      
      , <code>
        messageEncoding
      </code>
      
      , <code>
        suffix
      </code>
      
      , <code>
        suffixEncoding
      </code>
      
      , <code>
        secretLength
      </code>
      
      , <code>
        secretLengthMax
      </code>
      
      , <code>
        parameters
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hashes_digest_identify
      </code>
    </td>
    
    <td>
      <a href="https://hashes.agntn.dev/guide/identify">
        Candidates
      </a>
      
       for a hash, by prefix or length
    </td>
    
    <td>
      <code>
        digest
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hashes_digest_search
      </code>
    </td>
    
    <td>
      <a href="https://hashes.agntn.dev/guide/search">
        The transform
      </a>
      
       of some words behind a digest
    </td>
    
    <td>
      <code>
        digest
      </code>
      
      , <code>
        encoding
      </code>
      
      , <code>
        words
      </code>
      
      , <code>
        minWords
      </code>
      
      , <code>
        maxWords
      </code>
      
      , <code>
        joiners
      </code>
      
      , <code>
        cases
      </code>
      
      , <code>
        algorithms
      </code>
      
      , <code>
        rounds
      </code>
      
      , <code>
        chains
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hashes_algorithms
      </code>
    </td>
    
    <td>
      The list, one family or category, or one algorithm's options
    </td>
    
    <td>
      <code>
        family
      </code>
      
      , <code>
        category
      </code>
      
       or <code>
        algorithm
      </code>
      
      , all optional
    </td>
  </tr>
</tbody>
</table>

`parameters` carries what an algorithm declares beyond encoding, key and salt: `rounds` and `chain` for every fixed-length digest, `passes` and `bits` for HAVAL, `seed` for xxHash, `N`, `r`, `p` and `keyLength` for scrypt, `iterations`, `digest` and `keyLength` for PBKDF2, `info`, `digest` and `keyLength` for HKDF, `digest`, `iterations`, `keyLength` and `ivLength` for EVP_BytesToKey, `memory`, `iterations`, `parallelism`, `keyLength`, `secret` and `associatedData` for Argon2. `hashes_hmac_compute` takes `parameters` too, for HAVAL's dials. No second round or `chain`, though. HMAC runs once. A model that isn't sure calls `hashes_algorithms` with the name first. The descriptions say so, because otherwise models guess.

## What a model reads

Two lines. The digest, then what it is:

```text
751e76e8199196d454941c45d1b3a323f1433bd6
hash160, hex, 20 bytes
```

For a KDF the second line also carries the salt and every cost, since an MCP client sees only this text and the next `hashes_verify` needs all of it:

```text
c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a
pbkdf2, hex, 32 bytes, iterations 4096, digest sha256, keyLength 32, salt 73616c74
```

Ten words used to cost ten calls. Now `input` takes a list too, in `hashes_compute`, `hashes_hmac_compute` and `hashes_verify`. Up to 64 inputs, one set of options. It reads like `sha256sum`:

```text
2 inputs, sha256, hex, 32 bytes
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824  "hello"
486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7  "world"
```

Each input comes back in quotes. A trailing space can't hide there. Whatever the digests share goes in the heading once. A salt the KDF drew stays on its own line. And `hashes_verify` with a list of candidates? It says which one it was: `MATCH: 1 of 2 inputs gives the expected sha256 digest`, then `match` or `mismatch` per line. `hashes_digest_identify` takes up to 16 hashes the same way.

One bad input doesn't sink the rest. Bytes NTLM can't read as text get an `ERROR` line, and the others still hash. The call fails only when nothing did.

`hashes_verify` answers `MATCH` or `MISMATCH` with both digests. Lengths that differ get a line of their own, with the parameter that fixes them. A wrong variant shouldn't pass for a wrong password. `hashes_algorithms` lists one line per algorithm, family, category, size and HMAC support included. The Pi and OMP extensions and the AI SDK also hand the host structured `details` next to the text.

## Bytes, not letters

`inputEncoding` is `utf8` by default. `hex` and `base64` hash the bytes they spell, which is what you want for a public key, a raw transaction or a script. Without it, `hash160` of a public key hashes 66 characters of text and answers with a perfectly valid digest of the wrong thing. `hex` takes no `0x` prefix.

`keyEncoding` does the same for an HMAC key. A BIP32 chain code keys the HMAC as 32 bytes, not as 64 letters.

## What they refuse

Every schema is closed. A misspelled argument is an error, not a quiet default:

```text
Invalid arguments: unknown property "salt_hex"; takes algorithm, input, inputEncoding, encoding, salt, parameters
```

That one matters. Dropping `salt_hex` would have hashed with a fresh random salt and a model would never know why its digest doesn't reproduce.

The bounds sit in the schema and get checked again in the executor, because a host is free to skip schema validation:

- input up to a million characters, key up to ten thousand
- a salt of at most 256 bytes in hex, and an empty one is fine
- at most eight `parameters` and a million `rounds`
- KDF costs, with the numbers in [Salted KDFs](https://hashes.agntn.dev/guide/kdf)

A list doesn't get a bigger budget. A million characters in all, and its cost times the count stays under one call's cap. So three PBKDF2 inputs at the default 600000 iterations of SHA-512 get refused, and the error says two fit.

Nothing is written anywhere and there's no network to reach.

## MCP

```bash
hashes mcp
claude mcp add hashes --scope user -- npx -y @agntn/hashes mcp
```

Or in a client's config:

```json
{
  "mcpServers": {
    "hashes": { "command": "npx", "args": ["-y", "@agntn/hashes", "mcp"] }
  }
}
```

stdio, every tool read-only and idempotent. A call that fails the schema or throws a `HashError` comes back as a tool error with the reason, and the session carries on.

## Remote MCP

No Node on your side, or a client that only takes a URL? This site serves the same seven tools over Streamable HTTP, at `https://hashes.agntn.dev/mcp`.

```bash
claude mcp add --transport http hashes https://hashes.agntn.dev/mcp
```

```json
{
  "mcpServers": {
    "hashes": { "type": "http", "url": "https://hashes.agntn.dev/mcp" }
  }
}
```

It's the Docus MCP server with the hash tools bolted on. They're the very definitions `hashes mcp` serves, handed over by `toToolkitTools` from `@agntn/tools`, so a call that works answers byte for byte like stdio. No copy of the tool list to drift out of date. Docus throws in two of its own. `list-pages` lists the pages of this site and `get-page` hands one back as Markdown, so a model can read up on [length extension](https://hashes.agntn.dev/guide/extend) before it tries one.

A bad argument reads the same here, too. The toolkit wants Zod, so each tool hands it a blank schema that waves any object through. `tools/list` still shows the real one. The library then does the checking, and anything the client typed comes back with control and bidi characters turned into spaces:

```text
Invalid arguments: unknown property "salt_hex"; takes algorithm, input, inputEncoding, encoding, salt, parameters
```

Look familiar? Same line as stdio, so still no fresh salt nobody asked for. No `arguments` at all? Stdio reads `{}`, and now the site does too, so `hashes_algorithms` needs nothing but its name. The limits are the same ones, with one exception. A worker gets 128 MB, so a KDF here stops at 64 MiB of memory, which still fits the Argon2 defaults and scrypt up to N 65536. Past that the call is refused before it allocates anything, with a hint to run it locally, where 256 MiB is fine.

The catch: every argument goes to a Cloudflare worker. The site doesn't store your arguments or write them to its logs, but that's a promise, not something you can check from your side. A password or an HMAC key sent here has left your machine. Test vectors and puzzle material are fine. Anything real gets `npx -y @agntn/hashes mcp`, which hashes on your machine and nowhere else.

## Pi and OMP

```bash
pi install npm:@agntn/hashes
omp install @agntn/hashes
```

Both extensions are declared in `package.json` and ship as source the host loads directly.

## AI SDK

```ts
import { generateText } from "ai";
import { hashTools } from "@agntn/hashes/ai";

await generateText({
  model,
  tools: hashTools,
  prompt: "What's the HASH160 of 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798?",
});
```

`hashTools` holds all of them under the same names. Each one is also exported alone, `hashComputeTool` and friends, if you want to hand a model fewer.


## Sitemap

See the full [sitemap](https://hashes.agntn.dev/sitemap.md) for all pages.
