User guide
Installation, client configuration, tuning and operations. If you have never used this server before, start with the Tutorial instead; this document is the reference you come back to.
Contents
- Install
- Connecting a client
- The tool surface — how many tools you see, and why
- Using the tools
- Transports: stdio and HTTP
- Environment variables
- Performance and tuning
- Security
- Troubleshooting
Install
Section titled “Install”Docker (recommended)
Section titled “Docker (recommended)”docker pull ghcr.io/doublegate/cyberchef-mcp_v3:latestdocker tag ghcr.io/doublegate/cyberchef-mcp_v3:latest cyberchef-mcpDocker Hub carries the same image as parobek/cyberchef-mcp. The GHCR package is
major-versioned: _v3 for 3.x, _v2 for 2.x, _v1 for the frozen 1.9.x line.
Offline, from a release tarball:
wget https://github.com/doublegate/CyberChef-MCP/releases/download/v3.0.0/cyberchef-mcp-v3.0.0-docker-image.tar.gzdocker load < cyberchef-mcp-v3.0.0-docker-image.tar.gzdocker tag ghcr.io/doublegate/cyberchef-mcp_v3:3.0.0 cyberchef-mcpFrom source:
docker build -f Dockerfile.mcp -t cyberchef-mcp .From a checkout
Section titled “From a checkout”npm installnpx grunt configTests # REQUIRED -- generates OperationConfig.json and src/node/index.mjsnpm run mcpBoth generated files are gitignored, so a fresh clone cannot start without that second command. Node >=24 <27 is required; the published image runs Node 26.
Verify
Section titled “Verify”echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \ | docker run -i --rm cyberchef-mcp | jq '.result.tools | length'The -i flag is required. Without stdin the container exits immediately, which looks like a
crash and is not one.
Connecting a client
Section titled “Connecting a client”The shape is the same everywhere: a command, and arguments that run it in the foreground.
Claude Desktop / Claude Code
Section titled “Claude Desktop / Claude Code”~/.claude/config.json (or the app’s MCP settings):
{ "mcpServers": { "cyberchef": { "command": "docker", "args": ["run", "-i", "--rm", "cyberchef-mcp"] } }}Cursor
Section titled “Cursor”Settings → MCP → Add New MCP Server
- Name:
cyberchef - Type:
command - Command:
docker - Args:
run -i --rm cyberchef-mcp
Any generic MCP client
Section titled “Any generic MCP client”{ "mcpServers": { "cyberchef": { "command": "docker", "args": ["run", "-i", "--rm", "cyberchef-mcp"] } }}Passing environment variables
Section titled “Passing environment variables”Add -e pairs to the args, before the image name:
"args": ["run", "-i", "--rm", "-e", "CYBERCHEF_TOOL_SURFACE=curated", "-e", "CYBERCHEF_OPERATION_TIMEOUT=60000", "cyberchef-mcp"]Without Docker
Section titled “Without Docker”{ "mcpServers": { "cyberchef": { "command": "node", "args": ["/path/to/CyberChef/src/node/mcp-server.mjs"] } }}No --openssl-legacy-provider is needed. v2.1.0’s guide said it was “not optional”; that was
wrong, and worse, it did not work. Most Node builds – the Docker image included – ship no legacy
provider module at all, so the flag printed Unable to load legacy provider. and changed nothing.
Exactly one operation reached OpenSSL for a legacy algorithm (LM Hash, via DES-ECB); as of v2.2.0
it computes that in pure JavaScript, so every operation works on a stock Node with no flags.
The tool surface — how many tools you see, and why
Section titled “The tool surface — how many tools you see, and why”tools/list is sent to the model on every request. Exposing everything costs roughly
100,000 tokens before the user has typed anything, and model tool-selection quality is known to
degrade well before that many definitions.
So the default is an index, not a catalogue. Measured on the serialised tools/list payload at
v2.4.0, not estimated:
CYBERCHEF_TOOL_SURFACE |
Tools in tools/list |
Payload | Approx. tokens |
|---|---|---|---|
index (default) |
28 | 19 KB | ~4,900 |
curated |
106 | 81 KB | ~20,700 |
all |
531 | 391 KB | ~100,000 |
The figures grew across v2.2.0-v2.4.0 as tools gained annotations, titles and fuller argument descriptions — the ratio between the three modes is what matters, and it has held.
Nothing becomes unreachable. cyberchef_bake runs any of the 504 operations by name, and three
navigation tools let a client find the name and its arguments:
cyberchef_categories 16 categories, with counts and examples (~2 KB) cyberchef_list_operations the operations in one category (~8 KB for 50) cyberchef_describe_operation full argument schema for the ones chosen (~1.6 KB each) cyberchef_bake runs itcyberchef_search short-circuits the walk when you already know roughly what you want.
Magic is exposed in every surface, including index. It is what you reach for before you
know what you are looking at, so making it three calls deep would invert the cost.
The four analysis tools are in every surface too — cyberchef_xor_key_length,
cyberchef_cyclic_pattern, cyberchef_hash_identify and cyberchef_rsa_attack. Unlike an
operation, none of them is reachable through cyberchef_bake: they are not in OperationConfig,
because each performs an analysis rather than a transformation. Hiding them behind a surface setting
would make them unreachable rather than merely inconvenient. They cost about 1,500 tokens together.
Fine-grained control:
CYBERCHEF_TOOL_ALLOWLIST="To Base64,From Base64,SHA2,Gunzip" # exactly these, overrides the modeCYBERCHEF_TOOL_SURFACE=all # everything, the pre-v2.1.0 behaviourCYBERCHEF_EXPOSE_ALL_OPS=true # historical alias for the aboveUpgrading from v2.0.0? The default changed. A client that hard-codes a tool name outside the index —
cyberchef_to_morse_code, say — will no longer find it intools/list. SetCYBERCHEF_TOOL_SURFACE=allto restore the old surface, or call the operation throughcyberchef_bake, which never stopped working.
Using the tools
Section titled “Using the tools”The three you will use most
Section titled “The three you will use most”| Tool | For |
|---|---|
cyberchef_bake |
Running a recipe — one operation or twenty. Reaches all 504. |
cyberchef_search |
Finding an operation by keyword. |
cyberchef_describe_operation |
Getting an operation’s exact argument names, types and defaults. |
Recipes
Section titled “Recipes”A recipe is an ordered list; each operation’s output feeds the next. Operation names are CyberChef’s display names, so a recipe copied from the web UI works unchanged.
{ "input": "chain me", "recipe": [ { "op": "To Hex", "args": { "delimiter": "None" } }, { "op": "To Upper case" }, { "op": "To Base64" } ]}Positional argument arrays are also accepted, which is the format the web UI exports.
Flow control
Section titled “Flow control”Fork, Merge, Jump, Conditional Jump, Label, Register, Subsection, Comment, Return
and Magic all work in recipes from v2.1.0 (before that they were advertised and always failed).
{ "input": "a,b,c", "recipe": [ { "op": "Fork", "args": { "split_delimiter": ",", "merge_delimiter": "-" } }, { "op": "To Upper case" } ] }→ A-B-C
Three argument rules worth knowing
Section titled “Three argument rules worth knowing”- Names come from the schema, not the UI label. SHA2’s “Size” is
size. Inputis exposed asinput_arg. 31 operations — every symmetric cipher — have an argument namedInputmeaning the input format. It is renamed to avoid colliding withinput, the data itself.- Keys and IVs carry an encoding.
"key": "00ff"uses the default encoding;"key": { "string": "hunter2", "option": "UTF8" }is explicit. Getting this wrong fails later, as a decryption error.
Saved recipes and batches
Section titled “Saved recipes and batches”cyberchef_recipe_create/_get/_list/_update/_delete/_execute/_export/_import/_validate/_test— recipes persisted toCYBERCHEF_RECIPE_STORAGE.cyberchef_batch— many calls in one request,parallelorsequential, with per-item failures reported rather than the batch abandoned.
Worked examples of all of this live in examples/ and are executed by CI.
Images, audio and binary results
Section titled “Images, audio and binary results”Not every result is text, and since v2.2.0 the server stops pretending otherwise.
- Image operations return an
imagecontent block.Generate QR Code,Render Image,Rotate Imageand the rest put their payload in adata:URI; before v2.2.0 the html-to-text conversion deleted it and you received an empty string. Readcontent[0].data(base64) andcontent[0].mimeType, notcontent[0].text. Play Mediareturns anaudioblock, the same way and for the same reason.- Video returns its
data:URI as text. MCP has no video content block, so the payload is handed over verbatim rather than stripped — unreadable, but recoverable. - Other binary stays as latin1 text by default, one character per byte. It looks like mojibake
and is byte-for-byte reversible:
str.charCodeAt(i)isbytes[i]. SetCYBERCHEF_BINARY_OUTPUT=base64if you would rather have base64.
Tool annotations
Section titled “Tool annotations”Every tool carries readOnlyHint, destructiveHint, idempotentHint and openWorldHint, so a
client can decide whether to ask you before running it. Nearly all 504 operations are pure
functions: read-only, non-destructive, idempotent, closed-world.
Two things are worth knowing before you configure auto-approval:
cyberchef_bakeis not read-only, deliberately. It runs whatever recipe you hand it, and a recipe may containHTTP requestwith a POST. If your client prompts on non-read-only tools it will prompt forcyberchef_bake. Calling operation tools directly avoids that — each is annotated from its own behaviour, socyberchef_to_base64is read-only and idempotent.HTTP requestandDNS over HTTPSare the only two operations that reach the network, and onlyHTTP requestcan write. They are the two to think hardest about.
Prompts: where to start when you do not know
Section titled “Prompts: where to start when you do not know”The tool list tells you what the server can do. It does not tell you what to do first, and with 504 operations that gap is wide. Prompts are five named workflows your client shows as slash commands or menu entries:
| Prompt | Use it when |
|---|---|
analyse-unknown-data |
You have a blob and do not know what it is. |
extract-iocs |
You need URLs, IPs, emails and domains out of a document or script, defanged. |
deobfuscate-script |
You have obfuscated PowerShell, JavaScript, VBScript or PHP. |
identify-hash |
You have a hash and need to know which algorithm produced it. |
decode-chain |
You know roughly what was done to the data and want it unwrapped. |
Each encodes the order a practitioner would actually work in, not a restatement of the tool list —
Magic before guessing, entropy before assuming a decode will help, defang before an indicator
reaches a ticket.
Resources: saved recipes without a tool call
Section titled “Resources: saved recipes without a tool call”Saved recipes are exposed as MCP resources at recipe://<id>, with a recipe://{id} template. A
client can browse, cache and attach them the way it attaches a file, which is usually what you want
from a saved recipe — reading one no longer costs a tool call your client might prompt for.
The URI is the recipe’s id, not its name, because names are not unique: two recipes may both be
called “decode”, and a name-keyed URI would silently return the wrong one. cyberchef_recipe_list
reports the ids.
Transports: stdio and HTTP
Section titled “Transports: stdio and HTTP”stdio is the default and suits one client per process. Diagnostics go to stderr and JSON-RPC to stdout, so piping stdout is safe.
Streamable HTTP serves many clients, each with its own session:
docker run --rm -p 3000:3000 \ -e CYBERCHEF_TRANSPORT=http \ -e CYBERCHEF_HTTP_HOST=0.0.0.0 \ -e CYBERCHEF_ALLOWED_HOSTS=localhost:3000,127.0.0.1:3000 \ cyberchef-mcpDNS-rebinding protection is on by default and permits the loopback names. Binding a non-loopback address means naming the hosts you will reach it by — see the HTTP transport guide, which explains why loopback is not an exemption.
Browser-based clients additionally need CYBERCHEF_ALLOWED_ORIGINS.
Environment variables
Section titled “Environment variables”Tool surface
Section titled “Tool surface”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_TOOL_SURFACE |
index |
index, curated or all. |
CYBERCHEF_TOOL_ALLOWLIST |
(unset) | Comma-separated operation names; overrides the mode. |
CYBERCHEF_EXPOSE_ALL_OPS |
(unset) | true = all, false = curated. Historical alias. |
CYBERCHEF_MAX_TOOL_DESCRIPTION |
240 |
Characters of description carried per tool. |
Limits and execution
Section titled “Limits and execution”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_MAX_INPUT_SIZE |
104857600 |
Maximum input, in bytes (100 MB). |
CYBERCHEF_OPERATION_TIMEOUT |
30000 |
Per-operation timeout, in ms. |
CYBERCHEF_MAX_REGEX_LENGTH |
1000 |
ReDoS screen: longest accepted regex pattern. |
CYBERCHEF_ENABLE_STREAMING |
true |
Progress notifications for large inputs. |
CYBERCHEF_STREAMING_THRESHOLD |
10485760 |
Input size at which streaming engages (10 MB). |
CYBERCHEF_BINARY_OUTPUT |
text |
base64 returns non-image binary as base64 instead of the default latin1 text. See “Binary results” below. |
Workers
Section titled “Workers”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_ENABLE_WORKERS |
false |
Route CPU-heavy operations to a worker pool. |
CYBERCHEF_WORKER_MIN_THREADS |
1 |
Minimum pool threads. |
CYBERCHEF_WORKER_MAX_THREADS |
4 |
Maximum pool threads. |
CYBERCHEF_WORKER_IDLE_TIMEOUT |
30000 |
Idle thread timeout, in ms. |
CYBERCHEF_WORKER_MIN_INPUT_SIZE |
1024 |
Smallest input worth handing to a worker. |
Caching, quotas and telemetry
Section titled “Caching, quotas and telemetry”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_CACHE_ENABLED |
true |
Cache operation results. |
CYBERCHEF_CACHE_MAX_SIZE |
104857600 |
Cache size cap, in bytes. |
CYBERCHEF_CACHE_MAX_ITEMS |
1000 |
Cache entry cap. |
CYBERCHEF_BATCH_ENABLED |
true |
Enable cyberchef_batch. |
CYBERCHEF_BATCH_MAX_SIZE |
100 |
Maximum operations per batch. |
CYBERCHEF_TELEMETRY_ENABLED |
false |
Collect timing/usage statistics. |
CYBERCHEF_RATE_LIMIT_ENABLED |
false |
Enable request rate limiting. |
CYBERCHEF_RATE_LIMIT_REQUESTS |
100 |
Requests allowed per window. |
CYBERCHEF_RATE_LIMIT_WINDOW |
60000 |
Rate-limit window, in ms. |
CYBERCHEF_MAX_CONCURRENT_OPS |
10 |
Concurrent operations allowed. |
Retries
Section titled “Retries”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_MAX_RETRIES |
3 |
Attempts before giving up on a retryable failure. |
CYBERCHEF_INITIAL_BACKOFF |
1000 |
First retry delay, in ms. |
CYBERCHEF_MAX_BACKOFF |
10000 |
Longest retry delay, in ms. |
CYBERCHEF_BACKOFF_MULTIPLIER |
2 |
Growth factor between retries. |
Streaming internals
Section titled “Streaming internals”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_STREAM_CHUNK_SIZE |
1048576 |
Bytes per streamed chunk. |
CYBERCHEF_STREAM_MAX_CHUNKS |
1000 |
Maximum chunks per operation. |
CYBERCHEF_STREAM_PROGRESS_INTERVAL |
100 |
Minimum ms between progress notifications. |
Recipes
Section titled “Recipes”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_RECIPE_STORAGE |
./recipes.json |
Where saved recipes live. |
CYBERCHEF_RECIPE_MAX_COUNT |
10000 |
Maximum stored recipes. |
CYBERCHEF_RECIPE_BACKUP |
true |
Keep a backup on write. |
CYBERCHEF_RECIPE_MAX_OPERATIONS |
100 |
Maximum operations in one stored recipe. |
CYBERCHEF_RECIPE_MAX_DEPTH |
10 |
Maximum nesting depth when validating a recipe. |
HTTP transport
Section titled “HTTP transport”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_TRANSPORT |
stdio |
stdio, http, or socket. |
CYBERCHEF_HTTP_HOST |
127.0.0.1 |
Bind address. |
CYBERCHEF_HTTP_PORT |
3000 |
Port. |
CYBERCHEF_HTTP_PATH |
/mcp |
Endpoint path. Anything else gets a plain 404. |
CYBERCHEF_ALLOWED_HOSTS |
(loopback names) | Host allowlist. DNS-rebinding protection is on by default; * disables it. |
CYBERCHEF_ALLOWED_ORIGINS |
(unset) | Origin allowlist; enables CORS. Browser clients need it. |
CYBERCHEF_MAX_SESSIONS |
100 |
Concurrent session cap. |
CYBERCHEF_SESSION_TIMEOUT |
1800000 |
Idle-session reap threshold (30 min). |
CYBERCHEF_HTTP_MAX_BODY |
4194304 |
Maximum request body (4 MiB). |
Socket transport (CYBERCHEF_TRANSPORT=socket)
Section titled “Socket transport (CYBERCHEF_TRANSPORT=socket)”The stdio binding over a stream rather than a pipe: a Unix domain socket or a loopback TCP port. Each connection is pinned to its own server instance, so two clients never share one — the socket is the session, and there are no session ids to manage.
| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_SOCKET_PATH |
(unset) | Unix domain socket path. Mutually exclusive with the port. Created mode 0600. |
CYBERCHEF_SOCKET_PORT |
(unset) | TCP port. Mutually exclusive with the path. |
CYBERCHEF_SOCKET_HOST |
127.0.0.1 |
TCP bind address. |
CYBERCHEF_SOCKET_MAX_CONNECTIONS |
16 |
Concurrent connection cap; further connections are dropped. |
CYBERCHEF_SOCKET_ALLOW_REMOTE |
false |
Required to bind a non-loopback address. |
CYBERCHEF_TRANSPORT=socket CYBERCHEF_SOCKET_PATH=/run/cyberchef-mcp.sock npx cyberchef-mcpThis transport has no authentication. It is the stdio binding, whose security model is that the
peer already has access to your process. On a Unix socket that is enforced by file permissions —
hence 0600, rather than whatever the umask would have produced. On TCP there is nothing enforcing
it at all, which is why a non-loopback bind is refused unless CYBERCHEF_SOCKET_ALLOW_REMOTE=true
is set. If you set it, put your own authentication in front.
A note on Unix socket paths: sun_path is a fixed 108-byte field (104 on macOS), and the kernel
rejects anything longer with a bare EINVAL that names the path but not the reason. The server
checks the length itself and says so plainly.
There is deliberately no WebSocket transport. MCP does not define one — the specification’s
transports are stdio and Streamable HTTP — and no SDK ships one, so it would be a private extension
no client could speak. See docs/planning/ROADMAP.md.
Logging
Section titled “Logging”| Variable | Default | Meaning |
|---|---|---|
LOG_LEVEL |
info |
trace, debug, info, warn, error, fatal. |
Deprecations
Section titled “Deprecations”| Variable | Default | Meaning |
|---|---|---|
CYBERCHEF_SUPPRESS_DEPRECATIONS |
false |
Silence deprecation notices. |
V2_COMPATIBILITY_MODE |
false |
Preview which calls v2 changes, without changing behaviour. |
Performance and tuning
Section titled “Performance and tuning”Start-up is about 1.3 seconds — building 504 operation schemas. With CYBERCHEF_TOOL_SURFACE
at its default the schema build is far smaller, so start-up and the per-request payload are both
cheaper than exposing everything.
Shutdown is prompt. The server exits within ~20 ms of finishing its work. (Before v2.1.0 two
leaked timers held the process open for a further 60 seconds after every request — a
Promise.race timeout that was never cleared, and a context-cleanup timer that was never
unref’d.)
Caching is on by default and keyed on operation plus arguments plus input. cyberchef_cache_stats
reports hit rate; cyberchef_cache_clear empties it.
Workers (CYBERCHEF_ENABLE_WORKERS=true) move CPU-heavy operations — AES, bcrypt, scrypt, Argon2, PBKDF2
— onto a thread pool, so a long operation does not block the event loop. Worth enabling if you run
key derivation at any volume; unnecessary otherwise.
Large inputs stream progress notifications above CYBERCHEF_STREAMING_THRESHOLD, so a client
can show progress rather than appearing to hang.
Security
Section titled “Security”Non-root execution
Section titled “Non-root execution”The container runs as the unprivileged node user, UID 65532 — Chainguard’s nonroot identity:
docker run --rm --entrypoint id cyberchef-mcp# uid=65532(node) gid=65532(node) groups=65532(node)--entrypoint id is needed because the image’s entrypoint is node; a bare docker run … id
would pass id to node as a script path.
Hardened invocation
Section titled “Hardened invocation”docker run -i --rm \ --read-only \ --tmpfs /tmp:size=100M \ --cap-drop=ALL \ --security-opt=no-new-privileges \ cyberchef-mcpWhat the server does for you
Section titled “What the server does for you”- ReDoS screening on every user-supplied regular expression, before it executes. This matters
because catastrophic backtracking blocks the event loop, so no timeout — including
CYBERCHEF_OPERATION_TIMEOUT— can interrupt it once started. - DNS-rebinding protection on by default for the HTTP transport.
- Session caps and body limits on HTTP, so an unauthenticated client cannot exhaust the process.
- Input size limits, and per-request resource quotas.
- Digest-pinned base images, an SBOM and Trivy scan attached to every release.
Report vulnerabilities per SECURITY.md.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause and fix |
|---|---|
| Container exits immediately | Missing -i. The stdio transport needs stdin. |
ERR_MODULE_NOT_FOUND for a Config file |
Run npx grunt configTests. |
A tool you expect is not in tools/list |
The default surface is index. Use cyberchef_bake, or set CYBERCHEF_TOOL_SURFACE=all. |
Input must be one of the following: Raw, Hex |
The argument is input_arg, not input. |
Cannot read properties of undefined (reading 'option') |
A key/IV needs { string, option }, or a plain string. |
Generate all hashes fails with error:0308010C |
You are on v2.1.0 or earlier. Upgrade; v2.2.0 removed the OpenSSL dependency. |
Invalid Host header over HTTP |
Set CYBERCHEF_ALLOWED_HOSTS for a non-loopback bind. |
| Browser client fails its preflight | Set CYBERCHEF_ALLOWED_ORIGINS. |
SlowBuffer is not defined in local tests |
Apply the avsc substitution — see the README. |
Getting diagnostics
Section titled “Getting diagnostics”LOG_LEVEL=debug npm run mcp 2>server.logLogs go to stderr, so redirecting them never corrupts the protocol stream on stdout.
See also
Section titled “See also”- Tutorial — a guided first hour
examples/— eight runnable, CI-tested scripts- Tool reference — every tool’s contract
- HTTP transport — multi-client, CORS, DNS rebinding
- Recipe management — the saved-recipe subsystem
- Architecture — how it fits together