v4.0.0 release notes
This is the first major since v3.0.0, and it exists for a reason the plan did not model.
Why a major, when the trigger watch says none is due
Section titled “Why a major, when the trigger watch says none is due”npm run check:v4-triggers was run first, as the ritual requires, and reported what it has reported
for four releases:
draft changelog EMPTY ("Changes since the most recent release will accumulate here.")current revision 2026-07-28, no successorSDK @modelcontextprotocol/{server,node,client} all 2.0.0conformance 0.2.0-alpha.11server.json schema 2025-12-11 still newestNothing fired, and this release cuts a major anyway — because the plan was wrong, not the measurement.
v4.0.0-plan.md defined four triggers, and every one is an external MCP specification event.
The principle was sound: SemVer says MAJOR means something broke, and inventing a break to reach a
round number is worse than staying on v3 — which is exactly why the v4.1.0, v4.2.0 and v4.3.0
charters correctly shipped as v3.9.0, v3.10.0 and v3.11.0.
The error is that the trigger set was exhaustively external, leaving no internal path to a major at all. So v4.0.0 waited on an event that might never arrive, removable surface accumulated with no mechanism able to remove it, and the version number stopped carrying information.
A major version is not only a response to someone else’s breaking change. It is also how a project retires its own past.
What is removed
Section titled “What is removed”cyberchef_migration_preview and cyberchef_deprecation_stats
Section titled “cyberchef_migration_preview and cyberchef_deprecation_stats”Two tools advertised on every surface to help a caller migrate to v2.0.0 — nine minors ago.
The state of the machinery behind them is the argument. Calling cyberchef_bake normally produced
exactly one deprecation record:
{ "code": "DEP007", "description": "WITHDRAWN: cyberchef_bake is NOT being renamed. It keeps its name.", "alternative": "No action required.", "withdrawn": true}A warning about a change that was cancelled, telling the caller no action is required. Of the eight
DEP codes, three were withdrawn in v2.0.0 and the rest described work that had shipped (Zod
validation, structured errors, unified config) or been decided against — DEP005 and DEP006
announced that positional recipe arguments would be removed, while AGENTS.md and the FAQ correctly
document that both argument forms work and are tested.
cost: 995 bytes of EVERY tools/list, on every surface 522 lines in src/node/deprecation.mjs, 580 lines of testsFor scale, cyberchef_pqc_identify — the entire new tool v3.11.0 shipped — cost 1,470 bytes.
cyberchef-migrate
Section titled “cyberchef-migrate”A published bin, so removing it is a genuine break. Its premise is false: it converts v1-format
recipes to v2, and both formats work. Verified through a real client rather than assumed:
v1 positional args [{op:"To Base64", args:["A-Za-z0-9+/="]}] -> "SGk="v1 bare string op ["To Base64"] -> "SGk="v2 named args [{op:"To Base64", args:{alphabet:"..."}}] -> "SGk="All three identical. It converted a working format into another working format.
CYBERCHEF_EXPOSE_ALL_OPS
Section titled “CYBERCHEF_EXPOSE_ALL_OPS”Documented as a historical alias since v2.1.0, and it silently outranked CYBERCHEF_TOOL_SURFACE
in both directions — so a value set once and forgotten kept serving all 544 tools regardless of what
the newer variable said. A silent alias for the most expensive configuration change this server has
is worth being explicit about. Use CYBERCHEF_TOOL_SURFACE=all.
It is inert, not merely unsupported, and a test pins both directions: an alias that half-works is worse than one that is gone.
The compatibility configuration section
Section titled “The compatibility configuration section”suppressDeprecations and v2CompatibilityMode went with the system they configured, and
tools.exposeAllOps went with the inert alias it set – leaving that mapping would have let a
config file set a variable nothing reads. Settings: 64 in 15 sections → 61 in 14. All three
fail closed: the server refuses to start and names the key.
Node 24
Section titled “Node 24”The floor moves to >=26 <27. CI has tested both ends of the range since v2.8.1; the image has
shipped Node 26 throughout.
What this buys
Section titled “What this buys”surface before (v3.11.0) after (v4.0.0) deltaindex 43 / 45,963 bytes 41 / 44,968 -2 tools, -995 bytescurated 121 / 109,209 bytes 119 / 108,214 -2 tools, -995 bytesall 546 / 426,367 bytes 544 / 425,372 -2 tools, -995 bytesThe round-trip multiplier improved for the first time since v3.2.0 — index plus one operation
schema is now 9.1x cheaper than all, up from 8.9x. That figure has fallen at every release
that added a registry tool (18.2x → 9.5x → 9.2x → 9.0x → 8.9x), because a registry tool has no
navigation path and must sit in the index. Removing two tools that navigate to nothing moved it
back. The mechanism working in the direction it was designed for.
The trigger that was missing
Section titled “The trigger that was missing”T-13 — accumulated removals, added to scripts/check-v4-triggers.mjs so it is executed rather
than remembered, like its four neighbours:
A major is due when surface still advertised to callers describes a migration that has completed, been withdrawn, or been decided against.
It is the only offline check in that script — the question is about this repository, not the ecosystem. It reads none today, and that zero is worth nothing on its own, so it was verified by planting the real defect (the removed tool declaration, put back) and watching it fire and exit 1.
The original kill criterion is not repealed. “A major exists because the protocol forced one” remains true of triggers A–D; it is simply no longer the only reason one may exist.
Two things the gates caught
Section titled “Two things the gates caught”The image is renamed on a major, and I had not done it. bump-version.mjs moved the version and
left cyberchef-mcp_v3:4.0.0 — a tag that will never be pushed, since mcp-release.yml derives the
GHCR package from the tag’s major. check:versions failed on five locations at once. This is exactly
the v3.0.0 defect that made the check assert the major and not only the version, and this is the
first major since — a gate built for a once-in-years event worked the first time that event
recurred.
A test that mutates a source file is a race. Verifying T-13 meant planting the defect somewhere.
The first attempt planted it in src/node/mcp-server.mjs with a restore in finally. It passed
alone, passed under coverage, and failed the full suite in a file it never touched:
FAIL tool-surface-figures > serves exactly the tool counts and byte sizes the table records expected 'curated tools: table says 119, server serves 120' to be ''Vitest runs test files in parallel, and that suite boots a real server from the file being planted. Fixed by copying the script and its inputs into a temp directory. The restore was never the problem; the window was, and a window is invisible to a single-file run.
Verification
Section titled “Verification”npm run lint zero errorsnpm run check:versions every location agrees, including the _v4 image renamenpm run check:server-json validates against 2025-12-11npm run check:v4-triggers nothing fired, T-13 includednpm test 241/241 Node API, 2289/2289 operationsnpm run test:mcp 1713/1713 across 74 files, twicenpm run test:coverage exit 0 — 96.08 lines / 89.43 branches / 96.54 functions / 95.1 statementsUpgrading
Section titled “Upgrading”Node 26 is now the floor. >=26 <27.
The image is renamed. v4.x publishes to ghcr.io/doublegate/cyberchef-mcp_v4; v3.x stays at
_v3 and is not superseded in place. Docker Hub remains the single un-suffixed
parobek/cyberchef-mcp.
If you call cyberchef_migration_preview or cyberchef_deprecation_stats, they are gone. There
is no replacement, because there is no migration left to preview: v2.0.0’s recipe and error formats
have been the only formats for nine minors, and both recipe argument forms are supported.
If you run cyberchef-migrate, it is gone. Your v1 recipes already work unchanged.
If you set CYBERCHEF_EXPOSE_ALL_OPS, it is now ignored, and the server says so at
startup rather than leaving you to wonder why the tool count collapsed:
WARN CYBERCHEF_EXPOSE_ALL_OPS=true is set and is IGNORED: it was removed in v4.0.0. The tool surface is "index" -- set CYBERCHEF_TOOL_SURFACE=all|curated|index to choose one.It reports rather than restores: honouring the variable again would undo the removal, and guessing what the operator meant is exactly what the silent alias did wrong. Check which surface you were actually getting, because the old variable outranked the new one.
If you set compatibility.suppressDeprecations or compatibility.v2CompatibilityMode in
cyberchef.config.json, remove them: configuration parsing fails closed on unknown keys, so the
server will refuse to start rather than ignore them.