Agents
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.
| Tool | Does | Arguments |
|---|---|---|
hashes_compute | Digest of text or bytes | algorithm, input, inputEncoding, encoding, salt, parameters |
hashes_hmac_compute | HMAC with a key | algorithm, input, inputEncoding, key, keyEncoding, encoding, parameters |
hashes_verify | Compare with an expected digest | algorithm, input, inputEncoding, expected, encoding, salt, parameters |
hashes_digest_extend | Length extension of a known digest | algorithm, digest, message, messageEncoding, suffix, suffixEncoding, secretLength, secretLengthMax, parameters |
hashes_digest_identify | Candidates for a hash, by prefix or length | digest |
hashes_digest_search | The transform of some words behind a digest | digest, encoding, words, minWords, maxWords, joiners, cases, algorithms, rounds, chains |
hashes_algorithms | The list, one family or category, or one algorithm's options | family, 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:
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:
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:
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:
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
parametersand a millionrounds - 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
hashes mcp
claude mcp add hashes --scope user -- npx -y @agntn/hashes mcp
Or in a client's config:
{
"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.
claude mcp add --transport http hashes https://hashes.agntn.dev/mcp
{
"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:
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
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
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.