At work, I’m currently running a CKEditor 5 migration from v26 to v47. On paper, just a “version bump”. In real life: around twenty in-house plugins, each stuffed with deep legacy imports like @ckeditor/ckeditor5-core/src/plugin, that all have to be swapped for the modern named exports of the flat ckeditor5 package. Multiply that by every plugin, add the config files that reference them, and you get a migration where the hard part isn’t technical but bookkeeping: knowing, at any moment, what’s done, half-done, or not started.
Tracking that by hand — or worse, asking an AI to re-read 200 files every session — is slow, expensive and error-prone. So I took the problem from the other end and turned it into an MCP server. It’s called ckeditor_audit and the code is public.
The trigger: what if I audited with an MCP?
An MCP (Model Context Protocol) server exposes tools that assistants like Claude can call directly. The idea: instead of the AI guessing my migration status by reading files one at a time, it queries a server that has already pre-indexed the whole project and answers in a single round-trip.
The server is read-only — it never writes to my code, it observes and reports. Two goals drove me: first, full visibility on migration progress; second, a drastic AI token saving. The two turned out to be linked: the fewer raw files the AI reads, the cheaper it gets, and the sharper its answers.
The core: migration audit
Two tools carry most of the value. audit_plugin returns a detailed report for a given plugin, and audit_all summarizes the whole project. Every plugin is sorted into one of three statuses:

migrated— no legacy import detected, the plugin is clean.partial— the trap: legacy and modern imports coexist in the same plugin. Exactly the kind of thing a human eye lets slip.not_migrated— legacy only, nothing has moved yet.
But the status isn’t enough: the report drills down to the technical detail. For every legacy pattern found, it gives the exact file and line, the matched legacy signature, and the suggested modern replacement. It also lists the config files that reference the plugin, distinguishing active references from those that only live in commented-out code — a classic blind spot of ad-hoc grep calls.
The most frequent pattern of this migration is this one:

// BEFORE (v26)
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
// AFTER (v47)
import { Plugin } from 'ckeditor5';
The argument that changes everything: token saving
Here’s the calculation that convinced me to write this server. Without it, to understand the state of a single plugin, the AI typically reads 3 to 10 files: the plugin source, its package.json, the config files using it. Across twenty plugins, that’s 6000 to 15000 tokens just for discovery, before a single line of migration work. And in a long session, every re-read restarts the meter.

The server, on the other hand, has already indexed everything. One audit_plugin call returns a report of about 200 tokens covering status, detected patterns and config references. The saving runs between 80 and 95% of discovery tokens per session. Each report even carries a token_savings field that quantifies what was just avoided. A nice bonus: coverage is better than a manual grep, because the server searches across all relevant file types (JS entry files, YAML configs, PHP constants, plugin registries) and even catches usages in commented-out code.
Not just an auditor: 33 search tools
Along the way, the server became a real Swiss-army knife for code navigation — 33 tools in total. Under the hood, two engines: ripgrep for blazing-fast text search (falling back to Python’s re if it isn’t installed), and ast-grep for structural search.
- Text search:
grep_code,grep_with_context,count_matches(to gauge scope before digging),multi_search(up to 10 queries in parallel). - Structural search:
ast_searchwith a pattern syntax likeclass $NAME extends Plugin, plusfind_class,find_method,who_calls,what_calls. - Git-aware navigation:
git_changed_files,grep_changedto restrict a search to changed files only.
The server also exposes a guided MCP prompt, migrate_plugin, which assembles the current status and ready-to-use migration instructions. And three slash commands for Claude Code: /ckeditor-audit (the dashboard), /ckeditor-migrate <plugin> (the full audit → suggest → apply → validate workflow) and /ckeditor-report (the final Markdown or JSON report).
Built to be reusable
My favorite part: nothing is hardcoded for my migration. The legacy → modern mapping table lives in a plain lib/data/patterns.json file — you add an entry (with "is_regex": true for a regular expression) without touching code. And a generic fallback rule catches any deep @ckeditor/ckeditor5-* import not covered by a specific entry, so nothing slips through.
The version labels are configurable too: v26 as source, v47 as target in my case, but those are just two environment variables. In other words, this server can serve as an audit backbone for any A → B migration of CKEditor 5, not just mine.
The takeaway
Since I started using ckeditor_audit, my migration lost its anxiety-inducing side: I always know what’s migrated, partial or not_migrated, my AI sessions cost a fraction of what they used to, and every fix is traceable. A very concrete work problem turned into a reusable tool — exactly the kind of detour that ends up saving time.
The code is open and feedback is welcome: github.com/oumarkonate/ckeditor_audit.
