npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

angular-signal-forms-migration-mcp

v0.8.1

Published

MCP server that detects Angular Reactive Forms constructs and advises on migrating them to Angular Signal Forms (recipes verified against v22). Detect-and-advise only — never edits your code.

Readme

signal-forms-migration-mcp

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, tells you which ones are a safe mechanical rename and which ones need a human decision, and hands back verified before→after recipes for each.

It detects and advises. It never edits your code.

This is the one architectural rule the server is built around. 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.

Docs provenance

Signal Forms is new and is not reliably present in any model's training data — which means recipes written from memory are wrong in ways that look right.

Every recipe in src/core/recipes.ts was verified against Angular v22 using the official Angular CLI MCP server (npx @angular/cli mcp), cross-checked against angular.dev. Anything that could not be confirmed is labelled UNVERIFIED — confirm on <url> in its caveats.

The recipes also compile. CI installs a real @angular/forms@22 and typechecks fixtures that exercise 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:

npm run verify:install   # installs a real Angular 22
npm run verify:recipes   # compiles the recipe API surface against it

That check is what proves disabled(path, { when }) is the v22 signature, and that the nested schema() + apply() composition works — the docs demonstrate neither.

Provenance is structured and required — no recipe can exist without it:

"provenance": {
  "verifiedAgainstVersion": 22,
  "retrievedISO": "2026-07-21",
  "sources": ["https://angular.dev/guide/forms/signals/validation"],
  "versionSensitive": true
}

It ships in the tool response, so the calling agent can judge how current the advice is. A CI test fails the build on any recipe with an empty sources list.

npm run docs:audit   # every recipe + version + date + sources; exits non-zero if stale

The target version lives in exactly one place, src/core/version.ts. Upgrading to a new Angular release starts by changing that line and running the audit — see REVERIFICATION.md for the full procedure.

Things this caught that memory gets wrong:

  • The binding directive is [formField] / FormFieldnot [control] / Control, which appeared in pre-release v21 material and is what models tend to reproduce.
  • disabled() / hidden() gained an options-object form on v22 ({ when: … }) and marked the bare-callback form @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 differs across releases carry a VERSION-SENSITIVE caveat naming the form each version takes. The server reads your project's Angular version where it can (exact version from node_modules, falling back to the declared range) and resolves those recipes against it; when the version cannot be determined, the caveat gives you both.

Recipes carry a caveats array. Read it — that is where the sharp edges live.

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.

Composes with the official Angular MCP server

This server is deliberately narrow: it knows about migration. Run it alongside the official @angular/cli MCP server, which knows about Angular. Your agent can pull findings and recipes from here, then use search_documentation / find_examples there to confirm anything current or project-specific before it edits.

Install

Requires Node.js 20+. No clone needed — npx fetches it on demand.

claude mcp add signal-forms-migration -- npx -y angular-signal-forms-migration-mcp

Or add it to any MCP client config:

{
  "mcpServers": {
    "signal-forms-migration": {
      "command": "npx",
      "args": ["-y", "angular-signal-forms-migration-mcp"]
    }
  }
}
git clone https://github.com/Alvi97/angular-signal-forms-migration-mcp.git
cd angular-signal-forms-migration-mcp
npm install
npm run build
claude mcp add signal-forms-migration -- node "$PWD/dist/server.js"

The transport is stdio, so stdout is reserved for the protocol; all logging goes to stderr.

Do not npm install this into your Angular app. It is a standalone process your editor spawns, not a library your project depends on — installing it adds a dev tool to your production dependencies. Use npx (above), which keeps it in a cache outside your project entirely.

Upgrading

If you installed with @latest (as shown above), there is nothing to do. npx re-resolves the version on every launch, so restarting your editor picks up new releases.

The server also checks for updates once a day and writes a one-line notice to stderr when a newer version exists — so you find out without having to look. It is throttled, has a 2-second timeout, never touches stdout, and stays silent on any failure. Turn it off with:

{
  "mcpServers": {
    "signal-forms-migration": {
      "command": "npx",
      "args": ["-y", "angular-signal-forms-migration-mcp@latest"],
      "env": { "SIGNAL_FORMS_MCP_NO_UPDATE_CHECK": "1" }
    }
  }
}

To see what is actually running:

npx angular-signal-forms-migration-mcp --version

If you installed without @latest, npx keeps serving whichever version it cached first. Repoint the config at @latest, or clear the cache with npm cache clean --force and restart.

Tools

find_form_candidates

Scans .ts and .html files (or a directory, recursively) and reports every Reactive Forms construct it finds.

{ "path": "/abs/path/to/src/app" }

Results are paged — 200 findings by default — and filterable with constructs and classification, so you can pull one decision at a time. Always check incomplete: it is non-null whenever a window or a filter narrowed the result, and names the call that returns the rest; null means this is the whole picture. A whole workspace does not fit in one context — before paging, a 60-component scan returned a 1.4 MB frame.

Returns one entry per file, each finding carrying construct, line, snippet, a classification of "mechanical" or "judgment", and the reason for that call. Template findings use a Template. prefix (Template.formControlName, …) and resolve to the templateBindings recipe.

node_modules, dist, .angular and *.spec.ts are skipped. TypeScript parses with the compiler API; templates use a quote-aware token scan (not a full Angular AST), so re-run the AOT build after editing a template — the compiler is the real check. Inline template: strings and CSS/SCSS are not scanned.

get_signalforms_recipe

Looks up a verified before→after recipe.

{ "construct": "FormBuilder.group" }

Accepts the exact construct names find_form_candidates emits, plus the spellings a human would type — fb.group, required, ValidatorFn, and so on, case-insensitively.

An unknown construct is not an error: you get { found: false, availableConstructs: [...] } so the agent can correct itself and retry.

analyze_migration_complexity

Summarises the whole job: how big it is, and where to start.

{ "path": "/abs/path/to/src/app" }

Returns totalFindings, byConstruct, the mechanicalCount / judgmentCount split, and suggestedOrder — files sorted simplest-first, so all-mechanical files come before any that need design decisions, and the smallest of those comes first. Migrating in that order means you establish the model shape on easy files before you hit the hard ones.

get_migration_report

Composes everything into one markdown document.

{ "path": "/abs/path/to/repo" }

Returns a report with the headline totals, a suggested file order, a construct table naming the recipe for each, the individual judgment calls with line numbers and reasons, and a "Read the caveats" section listing any version-sensitive recipe that actually applies to your codebase (it stays silent when none does).

It returns the markdown as a string. It does not write a file — saving it is your decision, not the server's.

verify_migration

Reads code you have already migrated and reports Signal Forms traps that compile and are still wrong.

{ "path": "/abs/path/to/src/app/checkout.component.ts" }

Run it after tsc, not instead of it — anything the compiler already reports is deliberately not repeated. What it covers is the gap: TS2774 catches if (f().invalid) but not !f().invalid, while (…), !!…, || false, or template interpolation. It also flags the v21 rule shape that v22 keeps as @deprecated (so the build stays quiet), pre-release API names, an AbstractControl left inside a form() model (NG01907), and Reactive Forms imports left behind — downgraded to info when the compat layer is genuinely in use.

Every finding carries the shipped file:line that backs it. The response always includes checksSkipped: droppedConstraint cannot be decided from the migrated file — a field with no rule looks exactly like a field that never needed one — so it is refused rather than guessed, with the reason. Silence would read as a pass.

It proves the absence of known defects, never correctness.

get_angular_upgrade_plan

Signal Forms needs Angular 21+. When your project is older, the migration cannot start — so this returns the upgrade plan instead.

{ "path": "/abs/path/to/repo", "level": 3, "material": true }

It asks the same questions angular.dev/update-guide does — application complexity (1 Basic, 2 Medium, 3 Advanced), and whether you use ngUpgrade, Angular Material or Windows — and returns before/during/after steps as markdown, plus the one-major-at-a-time command sequence.

None of the step text is written by this tool. It is Angular's own update-guide data, vendored verbatim from the Angular repo with the commit recorded, and reproduced with Angular's own filter logic. Every plan links back to the live guide, which stays authoritative.

It also tells you which of those questions are irrelevant to your version range — for 19 → 22 the Windows and ngUpgrade answers cannot change anything, because those steps stop at v9 and v19 respectively. The official guide asks anyway.

It handles any forward range the data covers — v2 to v22 — not just the Signal Forms prerequisite. { "fromMajor": 14, "toMajor": 17 } works, and both default sensibly: fromMajor to the detected version, toMajor to the version the recipes target.

Outside that range it refuses rather than guesses. A target above the newest known release, a downgrade, and a no-op range are all rejected with a message saying which — because an empty or partial plan reads as "nothing to do", which is the worst possible answer. The refusal for a too-new target names the vendored date and points at data:update-steps, so a genuinely new Angular release is a refresh rather than a dead end.

Refresh the vendored data with npm run data:update-steps — the covered range widens automatically, since it is computed from the data rather than hardcoded.

Example

> 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

Development

npm run typecheck   # strict tsc, no emit
npm run lint        # eslint, type-checked rules
npm test            # vitest
npm run check       # all three

The core (src/core/*) is pure and has no MCP dependency — detection takes an injected FileSystemPort, so it is unit-tested entirely in memory. src/server.ts only adapts core functions to the protocol.

Status

Feature-complete through M15: six tools ship, with doc-verified recipes covering basic constructs, arrays, runtime shape mutation, async validators, custom controls, the three RxJS stream tiers, reading/writing form state, submission, model-shape constraints, CSS status classes, spec-file migration, and the .html template layer (bindings, the <select multiple> blocker, and the silent error-key rename).

See ROADMAP.md for what is still deliberately not covered — inline template: strings, ngModel/template-driven migration (undocumented upstream), and a ts.Program-backed deep mode. Those are real gaps, and the report says so in its own "Scope" section rather than letting the totals imply completeness.

License

MIT