---
title: "Agents"
description: "The same fifteen tools over MCP and the Pi and OMP extensions. What the text carries and where the limits live. The puzzle files themselves. And the four that leave the process."
canonical_url: "https://puzzles.agntn.dev/guide/agents"
---
# Agents

> The same fifteen tools over MCP and the Pi and OMP extensions. What the text carries and where the limits live. The puzzle files themselves. And the four that leave the process.

## Fifteen tools, three surfaces

MCP server, Pi extension and OMP extension serve the same definitions from `src/tools.ts`, through the adapters of `@agntn/tools`. They answer identically. A fix lands once.

<table>
<thead>
  <tr>
    <th>
      Tool
    </th>
    
    <th>
      Does
    </th>
    
    <th>
      Arguments
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        puzzles_stats
      </code>
    </td>
    
    <td>
      Totals, status counts, prize sums and technique counts
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_collections
      </code>
    </td>
    
    <td>
      Every collection with its author and its count per status
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_authors
      </code>
    </td>
    
    <td>
      Every author with its kind, its collections and its puzzle count
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_author
      </code>
    </td>
    
    <td>
      One author's record: aliases, profiles, addresses, sourced facts and technique counts
    </td>
    
    <td>
      <code>
        key
      </code>
      
      , an author key or a collection key
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_solvers
      </code>
    </td>
    
    <td>
      Every named solver with its kind, its solves and the collections it also published
    </td>
    
    <td>
      none
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_solver
      </code>
    </td>
    
    <td>
      One solver's record: every solve with date and prize, profiles and sourced facts
    </td>
    
    <td>
      <code>
        key
      </code>
      
      , a solver key or a puzzle identifier
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_show
      </code>
    </td>
    
    <td>
      One puzzle's complete record
    </td>
    
    <td>
      <code>
        id
      </code>
      
      , <code>
        allTransactions
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_hints
      </code>
    </td>
    
    <td>
      The hints that hold for one puzzle, then the hint files it ships
    </td>
    
    <td>
      <code>
        id
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_stages
      </code>
    </td>
    
    <td>
      The stages of a multi-stage puzzle in the author's order, with pages, files and published answers
    </td>
    
    <td>
      <code>
        id
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_assets
      </code>
    </td>
    
    <td>
      The files a puzzle ships and the archived copies of the pages it cites, or one of them read
    </td>
    
    <td>
      <code>
        id
      </code>
      
      , <code>
        file
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_list
      </code>
    </td>
    
    <td>
      Puzzles filtered by address, collection, chain, status, technique and public key availability
    </td>
    
    <td>
      <code>
        address
      </code>
      
      , <code>
        collection
      </code>
      
      , <code>
        chain
      </code>
      
      , <code>
        status
      </code>
      
      , <code>
        technique
      </code>
      
      , <code>
        withPubkey
      </code>
      
      , <code>
        limit
      </code>
      
      , <code>
        offset
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_verify
      </code>
    </td>
    
    <td>
      A published key and its recipe against the stored address, one puzzle or every filter match
    </td>
    
    <td>
      <code>
        id
      </code>
      
      , or the <code>
        puzzles_list
      </code>
      
       filters without <code>
        limit
      </code>
      
       and <code>
        offset
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_balance
      </code>
    </td>
    
    <td>
      The live balance of a puzzle address
    </td>
    
    <td>
      <code>
        id
      </code>
      
      , <code>
        apiKey
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_watch
      </code>
    </td>
    
    <td>
      What the chain, and with <code>
        since
      </code>
      
       the source page, knows that the record doesn't
    </td>
    
    <td>
      <code>
        id
      </code>
      
      , <code>
        since
      </code>
      
      , <code>
        apiKey
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        puzzles_eligibility
      </code>
    </td>
    
    <td>
      The checklist before working on a prize, with a <code>
        missing
      </code>
      
       row per field nobody can fill
    </td>
    
    <td>
      <code>
        query
      </code>
      
      , <code>
        chain
      </code>
      
      , <code>
        apiKey
      </code>
    </td>
  </tr>
</tbody>
</table>

Every tool is annotated read only and idempotent. Four are open world. `puzzles_balance` reaches a block explorer. `puzzles_eligibility` does too. `puzzles_watch` does as well, and the Wayback Machine when it gets `since`. `puzzles_assets` lists offline. Give it `file` and it fetches from GitHub, then from the author's URL and the Wayback Machine. A host that lets a model call them should know that. The rest never leave the bundled dataset.

## MCP

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

Or in a client's config:

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

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

## Remote MCP

No Node on your side? This site runs the same tools over Streamable HTTP, at `https://puzzles.agntn.dev/mcp`. Give it to any MCP client that takes a URL.

```bash
claude mcp add --transport http puzzles https://puzzles.agntn.dev/mcp
```

```json
{
  "mcpServers": {
    "puzzles": { "type": "http", "url": "https://puzzles.agntn.dev/mcp" }
  }
}
```

It's the Docus MCP server with the fifteen tools added to it. `toToolkitTools` from `@agntn/tools/toolkit` hands Docus the same definitions `puzzles mcp` serves from `src/tools.ts`. So the answers match to the byte, and a new tool shows up here on the next deploy. Nothing to copy, nothing to forget. Two more come from Docus. `list-pages` lists the pages of this site and `get-page` returns one as Markdown.

A few things differ, though fewer than you'd think. The toolkit takes Zod schemas only. So every tool hands it a blank one that waves any object through, and `tools/list` still shows the real schema. A bad argument then fails the library's own check. Same words, same control characters stripped. No `arguments` at all? Stdio reads `{}`, and now so does the site. `puzzles_verify` takes an `id` here and nothing else. A filtered replay burns seconds of CPU, and the site's worker isn't anyone's CI. Run `puzzles mcp` for that, on your own CPU. `puzzles_balance` runs on the site's worker, which lends no key of its own. Ethereum doesn't need one, Blockscout answers. Pass `apiKey` and the lookup goes to Etherscan instead, with the key passing through the worker on the way. And a web page on another origin gets a 403. Clients that call from their own process, like Claude Code or Cursor, send no origin and get in.

A pair of tools lives only here, both for ChatGPT. In ChatGPT Desktop, type `@` and a bit of an id, say `bits/7`. The picker fills with puzzles. Pick one and your message carries a link to that exact record. No more spelling out `bits/71` and hoping the model finds it. That's `puzzles_mentions`, and ChatGPT keeps it away from the model. Its `_meta` marks it for the app. The server names it in the `openai/mentions` capability, right where ChatGPT looks. The whole id comes first, then a prefix, and open puzzles go ahead of solved ones. `puzzles mcp` doesn't serve it at all. Why would a terminal agent want a picker it can't click?

The other one you open yourself. `puzzles_registry` sits in ChatGPT's sidebar and as a tab in any thread. The whole registry is a click away, and nobody has to ask the model for it. It's an MCP App: the tool points at a `ui://` page built from one Vue file, and `_meta["openai/ui"]` lists both entrypoints.

Every collection is a row, the largest first. Pick one and its puzzles fold out with status and prize. Search takes a key, an author or a puzzle id, so `71` digs up every puzzle with 71 in its id. A name opens its page on this site. `ask` drops `Show me the puzzle bits/71.` into the chat.

And the model? It gets one line of totals. The rows ride in the result's `_meta`, which ChatGPT hands to the app and never to the model. A whole registry for fewer tokens than this paragraph.

## The limits live once

`id` is 1 to 100 characters. `file` 1 to 200. `collection` up to 50. `address` 1 to 128. `apiKey` up to 200. `since` 10 to 40, a date or an ISO 8601 moment. `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, so code that calls them straight from `@agntn/puzzles/tools` hits the same wall, as `InvalidArgumentError`. A limit of 600 is rejected, not clamped. A model that asked for 600 should learn the limit, not get 500 back and wonder.

## Read the next page

`puzzles_list` starts at `offset: 0`. The offset counts matching puzzles, after all filters, in dataset order. For `bits`, the default first page ends with `Next page: offset=50. Keep the same filters.` Pass that offset to get the next rows without reading the first page again. No next offset means the end. An offset at or beyond the match count returns an empty page.

An `address` narrowed by other filters can come back empty while a puzzle still pays to it. Then the page names that puzzle and the filter that dropped it: `zden/level-halv pays to this address, but its collection is zden (not bits).` Without that line a model reads the empty page as an address nobody recorded.

A typo empties the page too. Then the last line reads `No supported chain accepts this address; its checksum or format is off.` And `details` gets `addressValid: false`. A flipped character used to look like a wallet nobody had heard of. Nobody had. It doesn't exist.

```json
{ "collection": "bits", "limit": 50, "offset": 50 }
```

Offsets are nonnegative safe integers, up to 9007199254740991. Keep the same dataset and filters while paging. After updating the package or changing registered collections, start again at zero. The CLI's `list` command remains unpaged.

## What the text carries

An MCP client sees the text a tool returns and nothing else. So the text carries the whole record, one `name: value` line per field:

```text
bits/1  solved  0.001 BTC   1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
chain: bitcoin  address kind: p2pkh
hash160: 751e76e8199196d454941c45d1b3a323f1433bd6
public key: 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 (compressed)
private key: 0000000000000000000000000000000000000000000000000000000000000001 (hex)
wif: KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn
started: 2013-01-09 11:59:15
solved: 2013-01-10 02:54:44 (14h 55m)
pre-genesis: yes
transactions: 2
    funding 2013-01-09 11:59:15 0.03 BTC    9223da07e858c6f153fbb8a24db52374ca19d2639098207c71710610cfda808e
    claim   2013-01-10 02:54:44 0.03 BTC    3da9b8e4a9c056b22d4fd09784402fd1caab1ecf621ba074efc20dc03ff04277
claim: https://blockstream.info/tx/3da9b8e4a9c056b22d4fd09784402fd1caab1ecf621ba074efc20dc03ff04277
collection hints: 1
    official    2017-04-27 06:41:08 There is no pattern. It is just consecutive keys from a deterministic wallet (masked with leading 000...0001 to set difficulty).    source: https://bitcointalk.org/index.php?topic=1306983.msg18765941#msg18765941 confirmation: https://web.archive.org/web/20200509045914/https://bitcointalk.org/index.php?topic=1306983.msg18765941 (Wayback capture of the thread page)
explorer: https://blockstream.info/address/1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
source: https://bitcointalk.org/index.php?topic=5218972
key range: 1..1 (hex, 1 bit)
```

The key prints in every form the record has. Hex and WIF here, a BIP38 payload with its passphrase on a Ballet card, an xpub with the published shares on bitaps. Then the dates, the solver, the transactions with their txids, the claim link, the assets and the hints, with the collection's hints ahead of the puzzle's own. Absent things say so: `public key: unknown`, `private key: unknown`. A line that vanishes reads as not shown, and a model fills it from memory.

Dust is the first thing that gets squeezed. Strangers keep sending tiny amounts to famous prize addresses, and gsmg got 120 of them since July 2024, most of them right at the dust limit. Nice of them, but listed one by one they were nearly two thirds of its answer. So a run of more than three increases, each under 1% of the largest transaction, prints as one line with the count, the first and last date and the total. The count line still says how many went in, `transactions: 127, 120 small increases folded`, and an author's top up keeps its own line, because that's the one a model actually needs. `allTransactions: true` brings every txid back, `puzzles show --all-transactions` does the same on the CLI.

Long hint lists are the second. The genesis author answered questions paid to the prize address with new hints, right up to the sweep. So that list kept growing, and in `puzzles_show` it was almost the whole answer. Then the model called `puzzles_hints` and got the same list again. Why pay for it twice?

A block of more than three hints now prints as one line, `hints: N, folded, puzzles_hints lists them`. Three or fewer still print in full, and that's most records. The CLI folds the same block and points at `puzzles hints <id>`.

Stages are the third. GSMG runs in seven phases, and their block was four fifths of its `puzzles_show` answer. Seven phases of ciphertext pages in a summary? That isn't a summary anymore, and walking them is what `puzzles_stages` is for. So past three, the `stages` block folds the same way, `stages: 7, folded, puzzles_stages lists them`. `teikhos/4` has three and still prints them all. On the CLI the pointer says `puzzles stages <id>`.

`puzzles_list` prints `12 of 77 matching puzzles:` when the limit cut the list, followed by the rows and a next offset. `puzzles_collections` prints the same rows as `puzzles collections`. A model learns the keys before it guesses one.

## The files, not a link to them

A record cites pages. Pages rot. Reddit threads lose comments, and the Satoshi birthday quiz kept its whole answer key in one. So what does a model do with a URL that answers 404?

It asks `puzzles_assets`. Without `file` it lists what the puzzle has. First the files the record ships: image, hint files, solution, stage artifacts. Each one with the size and SHA-256 the record pins. Then the archived sources. Those are the reading copies this repo keeps of the cited pages, with the transcript and every surviving comment:

```text
quizchain2/34: 0 files, 3 archived sources
source  assets/sources/quizchain2/aoinakamoto-2019-06-09-byqc7s.md  published 2019-06-09    cites https://www.reddit.com/r/Grycoin/comments/byqc7s/7_mbtc_quizchain2_block_34/  screenshot .png
source (author) assets/sources/satoshi-birthday-quiz/aoinakamoto-2019-04-05-b9l37o.md   published 2019-04-05    cites https://www.reddit.com/r/Bitcoin/comments/b9l37o/ screenshot .png
```

Every source has a screenshot, and it used to get a row of its own. Same path, `.png` on the end, nothing new. Over the whole dataset that was a fifth of every listing spent saying each path twice. Now it's the `screenshot .png` column on the source's row. Want the image? Swap `.md` for `.png` and pass that as `file`. `details.files` and `--json` still list it.

Pass one of those paths as `file` and the content comes back. Text as text. A PNG, JPEG, GIF or WebP as an image block. A model with eyes looks at the actual puzzle, not at somebody's description of it. The read tries the repository copy under the release tag first. A file merged after the last release isn't there yet, so `main` comes next, instead of a 404 that lasts until somebody cuts a release. Run it from a checkout, or through puzzles.agntn.dev, and the link names the running commit instead. A collection merged this morning? Its files are there on the first read, no release ceremony. For a pinned file it then tries the author's URL and the archive capture. Only bytes that match the pin come back. A copy with other bytes isn't the puzzle, however close it looks. If nothing matches, the error names every URL it tried and what each one answered.

So `puzzles_show`, `puzzles_stages`, `puzzles_hints` and `puzzles_eligibility` don't hand a model download links. A file prints as its path, `asset: assets/gsmg/puzzle.png`, and the answer ends with one line that says where to take it:

```text
files: 6 files, 0 archived sources; puzzles_assets lists them and reads one by its path
```

Why not the link? Give a model a URL and a shell, and it'll `curl` the thing. Those bytes never meet the pin. The archived sources don't show up in a record at all, so the count is what tells a model they exist. The CLI keeps its links, though. A person in a terminal wants something to click.

A `(author)` row is a page about the collection's author, so every puzzle of that author lists it. The screenshots are 2026 renders, fine for checking a transcript and useless for image analysis. An image block caps at 3.75 MiB. Zden's 20 MB codex protocol PNG? An error with the URL to download. The CLI has no such cap. `puzzles assets gsmg --read assets/gsmg/puzzle.png > puzzle.png` just writes it.

## What sets isError

An unverifiable puzzle is an answer. `puzzles_verify` on `bits/71` returns `bits/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

```bash
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 and every hint that holds for it for a show, `{ id, hints, hintAssets }` for a hints call, `{ id, stages }` for a stages call, `{ matched, returned, offset, ids }` plus `nextOffset` when more matches remain for a list, the verify result, the balance in base units as strings. A files listing adds `{ id, files }`, a read `{ id, file, bytes, sha256, servedBy }`. A filtered verify gives `{ matched, misses, results }`. MCP drops `details` and keeps the text. OMP gets the same JSON Schema, labels the tool group `Puzzles`, and renders a call line like `Show Puzzle: bits/71` with control characters stripped.

The extensions load the shared definitions 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.

<note>

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.

</note>


## Sitemap

See the full [sitemap](https://puzzles.agntn.dev/sitemap.md) for all pages.
