Guide

Agents

The same five tools over MCP and the Pi and OMP extensions. What the text carries and what sets isError. What a model can't hand the process.

Five tools, three surfaces

MCP server, Pi extension and OMP extension all call the same executors in src/tool-operations.ts. They answer identically and a fix lands once.

ToolDoesArguments
chains_lookupResolve a chain and return its metadatachain
chains_address_validateCheck an address against one chain's formatchain, address
chains_txid_validateCheck a transaction id against one chain's formatchain, txid
chains_address_identifyRun an address through every validatoraddress
chains_listThe registry, optionally one familyfamily optional

chain is a key, name, symbol or alias, the same thing getChain takes. Every tool is annotated read only, idempotent and closed world, because none of them touches anything outside the process.

MCP

shell
chains mcp
claude mcp add chains --scope user -- npx -y @agntn/chains mcp

Or in a client's config:

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

The server speaks MCP over stdio and checks every call against the tool's JSON Schema before an executor sees it: chain is 1 to 64 characters, address and txid 1 to 256, family 1 to 32. A model can't hand the process a novel to decode. createMcpServer() is exported from @agntn/chains/mcp for hosts that bring their own transport.

What the text carries

An MCP client sees the text a tool returns and nothing else, so the text carries the whole answer. chains_lookup prints every metadata field on a hit:

text
Polygon PoS (polygon)
symbol: POL
decimals: 18
type: evm
chainId: 0x89
caip2: eip155:137
bip44: 60
explorer: https://polygonscan.com
rpc: https://polygon-bor-rpc.publicnode.com

Absent fields say so out loud, bip44: none (no registered SLIP-0044 coin type), rather than vanishing, because a missing coin type reads as "not shown" and invites the caller to supply one from memory. A derivation path on an invented coin type silently produces the wrong addresses.

When resolution fails the text names the registered keys, so the next call has somewhere to go. chains_list exists for the same reason: without it the only way to learn what the registry holds is to send a value you expect to fail.

What sets isError

A valid UTXO address also says what it pays to: Valid Bitcoin (bitcoin) address: "bc1q…" - p2wpkh, hash160 751e…. details.decoded holds the same as an object. Why make a model decode the hash a second time? A rejected address is an answer, not a tool error. chains_address_validate on a bad address returns Invalid Bitcoin (bitcoin) address: "…" with isError unset. On an EVM chain, Bitcoin and the chains that share its Base58Check or SegWit encodings the line ends with the reason, like - 39 hex digits after 0x, not 40. Only an unresolvable chain or a chain with no validator sets isError, because then nothing was checked. chains_txid_validate works the same way, and there every chain adds the reason. Every chain that ships checks txids, so the missing validator is only ever a custom chain that skipped assertTxid, and a call against it is a tool error, not a rejection. chains_address_identify never sets it, a miss everywhere is still an answer.

The address in an answer comes back quoted. It arrives from whatever the caller was reading, and a newline inside one would otherwise write its own line, so a rejected address could read as a match on the chain about to be funded.

Pi and OMP

shell
pi install npm:@agntn/chains

Both extensions are declared in package.json. They add details next to the text, the structured object the harness renders: the metadata for a lookup, { chain, address, valid, reason } for an address check and { chain, txid, valid, reason } for a txid, the two key arrays for identify. MCP drops details and keeps the text.

A call that checked nothing reaches OMP with isError set. Pi reads every returned result as a success, flag or not, so the Pi extension throws instead. The model still gets the same text, marked as a failed call and without details.

The extensions prefer the built executors in dist/ and fall back to source when it's missing. The source runs as it is, so a fresh checkout answers before the first pnpm build too.

An address in a prompt is data. Validating it tells you whether it fits a format, not what to do with it. And a match on fourteen EVM chains is fourteen possibilities, not a chain name.