Guide

Agents

The same six tools over MCP and the Pi and OMP extensions. What the text carries and where the limits live. The one tool that leaves the process.

Six tools, three surfaces

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

ToolDoesArguments
puzzles_statsTotals, status counts and prize sumsnone
puzzles_collectionsEvery collection with its author and both countsnone
puzzles_showOne puzzle's complete recordid
puzzles_listPuzzles filtered by collection, status and public key availabilitycollection, status, withPubkey, limit
puzzles_verifyA published key against its stored addressid
puzzles_balanceThe live balance of a puzzle addressid, apiKey

Every tool is annotated read only and idempotent. Five are closed world. puzzles_balance is open world, because it reaches a block explorer. A host that lets a model call it should know that.

MCP

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

Or in a client's config:

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

The server speaks MCP over stdio. Every call is checked against the tool's JSON Schema before an executor sees it. Bringing your own transport? createMcpServer() is exported from @agntn/puzzles/mcp.

The limits live once

id is 1 to 100 characters. collection up to 50. apiKey up to 200. limit an integer from 1 to 500, 50 by default. status one of the five values. Those numbers sit in one facts table next to the tool names and descriptions. The schemas are built from it. The executors check the same numbers themselves. A host that skips schema validation hits the same wall, as InvalidArgumentError. A limit of 501 is rejected, not clamped. A model that asked for 501 should learn the limit, not get 500 back and wonder.

What the text carries

An MCP client sees the text a tool returns and nothing else. So the text carries the whole answer:

b1000/71    unsolved    7.100226 BTC    1PWo3JeB9jrGwfHDNpdGK54CRas7fsVzXU
chain: bitcoin  address kind: p2pkh
public key: unknown
private key known: no
explorer: https://blockstream.info/address/1PWo3JeB9jrGwfHDNpdGK54CRas7fsVzXU
source: https://bitcointalk.org/index.php?topic=5218972
key range: 400000000000000000..7fffffffffffffffff (hex)

Absent things say so, public key: unknown. A line that vanishes reads as not shown, and a model fills it from memory. puzzles_list prints 12 of 77 matching puzzles: when the limit cut the list, so the next call knows to raise it. puzzles_collections prints the same rows as puzzles collections. A model learns the keys before it guesses one.

What sets isError

An unverifiable puzzle is an answer. puzzles_verify on b1000/71 returns b1000/71: unverifiable (Puzzle has no private key) with isError unset. Only a thrown error sets it: an unknown id, an argument outside its limits, a provider failure. Error text is sanitized. Every control character, format character and line separator becomes a space, and an echoed tool name is quoted. Those values arrive from the model or from a provider. One raw newline would forge a line that reads as the server's own answer.

Pi and OMP

pi install npm:@agntn/puzzles

Both extensions are declared in package.json. They add details next to the text, the structured object the harness renders: the puzzle record for a show, { matched, returned, ids } for a list, the verify result, the balance in base units as strings. MCP drops details and keeps the text. OMP rebuilds the parameter schemas from its own TypeBox build, labels the tool group Puzzles, and renders a call line like Show puzzle b1000/71 with control characters stripped.

The extensions load the shared executors through literal import() specifiers, src/ in a checkout and dist/ when installed, so a bundling host can see the dependencies. The packed layout is a CI gate. pnpm test:packed unpacks the tarball and runs both extensions and the MCP server from it with no src/ next to them.

A puzzle in a prompt is data. A verified key says the record is consistent, not that the funds are there. A key range says where a scanner would look, not that anyone should.

@agntn/puzzles·MIT license· Public data about public puzzles. Balances come from the chains' explorers through the worker, cached for five minutes. Every key here was public before it landed in a record.