Guide

Agents

The same four 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.

Four 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_validate_addressCheck an address against one chain's formatchain, address
chains_identify_addressRun 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

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

Or in a client's config:

{
  "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 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:

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 rejected address is an answer, not a tool error. chains_validate_address on a bad address returns Invalid Bitcoin (bitcoin) address: "…" with isError unset. Only an unresolvable chain or a chain with no validator sets isError, because then nothing was checked. chains_identify_address 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

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 a check, the two key arrays for identify. MCP drops details and keeps the text.

The extensions prefer the built executors in dist/ and fall back to source only when it's missing. Without pnpm build the tools still register and the first call dies with a module resolution error, which is the kind of thing worth knowing before a demo.

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 thirteen EVM chains is thirteen possibilities, not a chain name.

@agntn/chains·MIT license· A format check, not proof an address exists. Nothing on this site talks to a chain.