Guide

Agents

The same five tools over MCP, the Pi extension and the OMP extension. What they validate, what they bound.

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, which is the whole reason the file exists.

ToolDoesArguments
cipher_encodeEncode with a cipher named exactlycipher, text, plus the cipher's options
cipher_decodeDecode with a cipher named exactlysame
cipher_brute_caesarEvery shift from 1 to 25text
cipher_frequencyHistogram and index of coincidencetext, language
cipher_infoList the ciphers, or one cipher's optionscipher optional

Option arguments mirror the library: shift, key, rails, a, b, period, positions, rings, plugboard, preserveCase, stripNonAlpha. A model that doesn't know a cipher's options calls cipher_info first. Every tool description says so, because models guess otherwise.

MCP

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

Or in a client's config:

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

The server speaks MCP over stdio and checks every call against the tool's published JSON Schema before a cipher sees it. shift is an integer 1 to 25, rings and positions are three letters, text and key have a maximum length. Fails the schema - tool error with the reason. Throws a CipherError - also a tool error. The session survives either way.

Every tool answers with text. cipher_encode and cipher_decode return the output and, in details, the cipher, the operation and the options that were applied. cipher_brute_caesar returns 25 lines. cipher_frequency returns the same table the CLI prints. cipher_info with no name is one line per cipher with its family.

Pi and OMP

pi install npm:@agntn/ciphers

Both extensions are declared in package.json. In the terminal a result shows as a collapsed preview cut to a few lines, with a count of what was left out, and the full text once you expand the row. Brute force is 25 lines, so this matters more than it sounds.

Bounds

Text is bounded per tool and keys are bounded too, in the schema. A model cannot hand the process a novel to shift. Nothing is written anywhere and there is no network to lose. One cipher keeps state: columnar caches its last 128 answers per instance and refuses more than 100 calls a second with a CipherError, which comes back as a tool error like any other.

A ciphertext in a challenge is data. Decoding it tells you what it says, not what to do. An agent that decodes IGNORE PREVIOUS INSTRUCTIONS learned one thing about the puzzle and nothing about its task.

@agntn/ciphers·MIT license· Classical ciphers, for lessons and puzzles. Not for protecting anything, ever.