# angular-signal-forms-migration-mcp [![npm](https://img.shields.io/npm/v/angular-signal-forms-migration-mcp.svg)](https://www.npmjs.com/package/angular-signal-forms-migration-mcp) [![node](https://img.shields.io/node/v/angular-signal-forms-migration-mcp.svg)](https://nodejs.org) [![license](https://img.shields.io/npm/l/angular-signal-forms-migration-mcp.svg)](./LICENSE) An MCP server that helps an AI coding agent migrate **Angular Reactive Forms** to **Angular Signal Forms**. It finds the Reactive Forms constructs in your codebase, separates the safe mechanical renames from the ones that need a human decision, and hands back before→after recipes that are verified against a real Angular release rather than written from memory. ## Install Requires Node.js 20+. Nothing to clone — `npx` fetches it on demand. ```bash claude mcp add signal-forms-migration -- npx -y angular-signal-forms-migration-mcp@latest ``` Or add it to any MCP client config: ```json { "mcpServers": { "signal-forms-migration": { "command": "npx", "args": ["-y", "angular-signal-forms-migration-mcp@latest"] } } } ``` `@latest` makes `npx` re-resolve on every launch, so restarting your editor picks up new releases. Without it, npx keeps serving whichever version it cached first — repoint the config at `@latest`, or clear the cache with `npm cache clean --force`. The server also checks for a newer version once a day and writes a one-line notice to **stderr**. It is throttled, times out after 2 seconds, never touches stdout, and stays silent on any failure. To turn it off, add `"env": { "SIGNAL_FORMS_MCP_NO_UPDATE_CHECK": "1" }` to the config block above. To see what is actually running: ```bash npx angular-signal-forms-migration-mcp@latest --version ``` > [!IMPORTANT] > **Do not `npm install` this into your Angular app.** It is a standalone process your > editor spawns, not a library your project depends on. `npx` keeps it in a cache outside > your project entirely. > [!NOTE] > **It detects and advises. It never edits your code.** There is no tool here that writes > to your source files, and there never will be. The server returns findings and recipes; > your agent decides what to change and makes the edits, so every change still goes > through your normal review and version control. ## What it looks like ``` > Migrate the forms in src/app/checkout to Signal Forms. 1. find_form_candidates { path: ".../src/app/checkout" } → 9 findings across 2 files: 6 mechanical, 3 judgment (the FormArray of line items is judgment — its shape changes) 2. get_signalforms_recipe { construct: "FormBuilder.group" } → before/after + caveats 3. the agent applies the edits, you review the diff 4. verify_migration { path: ".../checkout.component.ts" } → traps that compile and are still wrong ``` ## Prerequisites it checks before advising anything A migration report leads with the things that make migration impossible, because a plan you cannot execute is worse than no plan: 1. **Angular 21+.** `@angular/forms/signals` does not exist before v21. Below that the server returns an upgrade plan instead of a migration plan. 2. **Declared and installed versions agree.** An old branch checked out over newer `node_modules` straddles the v21 line. Migrating against what is installed means the next `npm ci` reverts you to a version where the target API is absent — so the server refuses to pick a side and says so. 3. **`moduleResolution` is exports-aware** (`bundler`, `node16`, or `nodenext`). `@angular/forms/signals` is a package-exports subpath; legacy `node` resolution cannot see it, and the import fails with an error that looks like a missing dependency. ## Tools | Tool | What it answers | | ------------------------------ | ----------------------------------------------------------------- | | `find_form_candidates` | Where are the Reactive Forms constructs, and which need a person? | | `get_signalforms_recipe` | What is the verified before→after for this construct? | | `analyze_migration_complexity` | How big is this job, and which file should I start with? | | `get_migration_report` | Give me the whole thing as one markdown document. | | `verify_migration` | I already migrated this — what compiles but is still wrong? | | `get_angular_upgrade_plan` | I am below v21. How do I get to a version that supports this? | Full parameters, response shapes and scope limits: **[docs/TOOLS.md](./docs/TOOLS.md)**. ## How the recipes are verified Signal Forms is new and is not reliably present in any model's training data — recipes written from memory are wrong in ways that look right. So none of these are. Recipes are verified against **Angular v22** using the official Angular CLI MCP server, cross-checked against angular.dev, and carry structured provenance (`verifiedAgainstVersion`, `retrievedISO`, `sources`) that ships in the tool response so your agent can judge how current the advice is. A recipe with an empty `sources` list fails CI. Where the docs do not answer the question, the recipe **says so in its caveats** rather than inventing an answer — `UNVERIFIED — confirm on `, or `UNVERIFIED — tool-authored guidance` where Angular documents no migration path at all (the RxJS stream tiers are the main case: the primitives are documented and compile, but choosing between them is this tool's judgement, not Angular's). **14 of 37 recipes** currently carry such a marker. That is the honest part of the output, not an oversight — and a test keeps this number true. **The recipes also compile.** CI installs a real `@angular/forms@22` and typechecks fixtures exercising every API the recipes use, so a recipe naming a function that does not exist — or calling it with the wrong argument shape — fails the build. That is what established `disabled(path, { when })` as the v22 signature; the docs demonstrate neither it nor the nested `schema()` + `apply()` composition. Two things this caught that memory gets wrong: - The binding directive is `[formField]` / `FormField` — **not** `[control]` / `Control`, which appeared in pre-release v21 material and is what models reproduce. - `disabled()` / `hidden()` gained an options-object form on v22 and marked the bare callback `@deprecated` rather than removing it — so a v21-shaped rule still compiles, with a warning. Established by diffing the shipped overloads, not the guides. Recipes whose behaviour genuinely differs across releases carry a `VERSION-SENSITIVE` caveat naming the form each version takes, and the server resolves them against your project's detected version. **Read the `caveats` array** — that is where the sharp edges live. The re-verification procedure for a new Angular release is [REVERIFICATION.md](./REVERIFICATION.md). ## It tells you when there is no clean answer Not every Reactive Forms pattern has a Signal Forms equivalent, and a migration tool that pretends otherwise is worse than none. Form streams are graded by the RxJS operators in their `.pipe()` chain: | Tier | Operators | Answer | | -------- | ------------------------------------------------------------- | -------------------------------------------------- | | trivial | none / bare `subscribe` | `computed()`, or `effect()` for a real side effect | | moderate | `map`, `filter`, `debounceTime`, `distinctUntilChanged`, … | `computed()` + the `debounce()` schema rule | | hard | `switchMap`, `combineLatest`, `withLatestFrom`, `forkJoin`, … | **no direct equivalent** | For the hard tier the recipe says so outright and offers three real strategies — async validation rules, `rxResource`, or keeping RxJS behind `toObservable`/`toSignal` — rather than inventing a one-liner that does not exist. Likewise `addControl()` / `removeControl()` have no counterpart at all: the field tree is derived from the model signal's type. The recipe explains the three actual answers instead of implying an API that would not compile. ## What it will not do - **Edit your files.** By design, permanently. - **Prove your migration is correct.** `verify_migration` proves the absence of _known_ defects. Run it after `tsc`, not instead of it. - **Migrate template-driven forms.** `ngModel` migration is undocumented upstream; guessing it would be the exact failure mode this project exists to avoid. - **Read template literals with `${...}` substitutions**, because their text is not what the Angular compiler sees. Inline `template:` strings without substitutions _are_ scanned. - **Scan CSS/SCSS**, or resolve types across files — detection is a single-file syntactic pass, not a `ts.Program`. [ROADMAP.md](./ROADMAP.md) tracks these; the migration report repeats them in its own "Scope" section rather than letting the totals imply completeness. ## Why not just the official Angular MCP server? Use both. The official `@angular/cli` MCP server knows about _Angular_; this one knows about _migration_ — which constructs exist in your code, which are mechanical, which need a decision, and what the verified replacement is. Your agent can pull findings and recipes from here, then confirm anything current or project-specific there before it edits. ## Status Feature-complete through M16. Six tools ship, with doc-verified recipes covering basic constructs, arrays, runtime shape mutation, async validators, custom controls, the three RxJS stream tiers, reading and writing form state, submission, model-shape constraints, CSS status classes, spec-file migration, and the `.html` template layer — bindings, state reads, the `