Guide

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 are exactly what a model reads.

ToolDoesArguments
hashes_computeDigest of text or bytesalgorithm, input, inputEncoding, encoding, salt, parameters
hashes_hmac_computeHMAC with a keyalgorithm, input, inputEncoding, key, keyEncoding, encoding, parameters
hashes_verifyCompare with an expected digestalgorithm, input, inputEncoding, expected, encoding, salt, parameters
hashes_digest_extendLength extension of a known digestalgorithm, digest, message, messageEncoding, suffix, suffixEncoding, secretLength, secretLengthMax, parameters
hashes_digest_identifyCandidates for a hash, by prefix or lengthdigest
hashes_digest_searchThe transform of some words behind a digestdigest, encoding, words, minWords, maxWords, joiners, cases, algorithms, rounds, chains
hashes_algorithmsThe list, one family or category, or one algorithm's optionsfamily, category or algorithm, all optional

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

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

shell
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.

shell
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 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

shell
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.