What it is for
The package already ships everything an agent needs to know: a .d.ts per entry point, the whole catalog as llms-full.txt, and a markdown twin of every docs page. What it does not ship is a way to ASK. An agent that does not already know an entry point's NAME cannot use a .d.ts — it cannot open types/ngwr-date-picker.d.ts to answer "does this library have a date range picker", because finding that filename was the question. And once it has the name, the file it wants can be 40 KB of declarations standing in for a forty-line answer — ngwr-table.d.ts is the record holder.
ngwr-mcp is a Model Context Protocol server that closes exactly that gap. It speaks JSON-RPC 2.0 over stdio, has no dependencies of its own, and ships as a bin in the ngwr package you already install.
Claude Code
A command, or a file you check in.
# From your project root. The server is a bin in the package you already
# depend on, so there is nothing extra to install.
claude mcp add ngwr -- npx -y ngwr-mcp The equivalent as a file — .mcp.json at the project root is the shared, committed form, so a teammate's agent starts with the same catalog rather than none:
// .mcp.json at the project root — check it in and every agent on the
// team gets the same catalog.
{
"mcpServers": {
"ngwr": {
"command": "npx",
"args": ["-y", "ngwr-mcp"]
}
}
}Claude Desktop
// macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"ngwr": {
"command": "npx",
"args": ["-y", "ngwr-mcp"]
}
}
}Restart the app after editing the file — the config is read at start-up.
Cursor
// .cursor/mcp.json in the project, or ~/.cursor/mcp.json for every project.
{
"mcpServers": {
"ngwr": {
"command": "npx",
"args": ["-y", "ngwr-mcp"]
}
}
}Pinning it to your installed copy
npx fetches the latest; node runs the one your app resolves.
npx -y ngwr-mcp resolves the newest published version, which is the right default for asking what the library can do. It is the wrong default when the answers have to match the version in your lockfile — an agent told about an input that only exists in a later major will write code that does not compile. Point the client at node_modules instead:
// Pinned to the copy in node_modules. The answers then come from the
// version your app actually resolves, and start-up fetches nothing.
{
"mcpServers": {
"ngwr": {
"command": "node",
"args": ["./node_modules/ngwr/mcp/server.js"]
}
}
} The path is relative to the working directory the client launches the server in, which for every client above is the project root. Both forms are the same file: npx downloads the package that ./node_modules/ngwr/mcp/server.js already sits inside.
The four tools
| Name | Description | Type | Default |
|---|---|---|---|
search_ngwr | Ranked search over the catalog. A name or selector hit outranks a description hit, because a description names neighbours — wr-drawer mentions the bottom sheet — and matching there is weaker evidence. Start here when you do not know the entry point name. | { query: string; limit?: number } | limit 10, capped at 40 |
get_ngwr_component | One entry point in full: description, selector, exports, import line, SCSS import, which of its exports have an API worth asking about, and both docs URLs. Resolves an entry point (ngwr/select, select), a symbol (WrSelect) or a selector (wr-select). | { name: string } | — |
get_ngwr_api | The inputs, models, outputs and methods of one class, read out of the .d.ts this package ships — the published signature rather than a copy of it. kind narrows the surface, which is the difference between forty lines and four. | { symbol: string; kind?: 'all' | 'input' | 'model' | 'output' | 'method' | 'property' } | kind 'all' |
get_ngwr_setup | The commands and providers for a set of symbols: install, the ng g ngwr:use invocation per symbol, the SCSS to load, and any provider they cannot work without. It returns the commands as text — it does not run them. | { symbols: string[] } | — |
Four, and deliberately not more. Each one answers a question an agent actually has when it meets the library: what is there, how do I use this one, what does it take, what do I have to install. A tool per docs page would be a worse version of fetch — the pages are prerendered HTML with a markdown twin at every URL, and any agent can already read those.
search_ngwr
Start here — it is the tool that turns a need into a name.
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_ngwr","arguments":{"query":"date range picker","limit":3}}}What comes back as content[0].text:
## ngwr/date-picker
selector: wr-date-picker, wr-date-range-picker
import: import { WrDatePicker } from 'ngwr/date-picker'
Unified date / time / date-time picker. `<input>` + popover for every mode — overlay content swaps based on `[mode]`. Parses on every keystroke (silently — only emits when valid), reformats canonical on blur. Format driven by `WrDateAdapter`.
## ngwr/date-picker/testing
import: import { WrDatePickerDayHarness } from 'ngwr/date-picker/testing'
## ngwr/color-picker
selector: [wrColorPickerTrigger], wr-color-picker
import: import { WrColorPicker } from 'ngwr/color-picker'
HSV canvas + hue / alpha sliders + HEX / RGB / HSL inputs + optional swatches. Use `<wr-color-picker>` inline or anchor it to a button with `[wrColorPickerTrigger]`. Two things to read out of that. The scoring is why ngwr/date-picker is first for a query that names none of its words exactly — its selector wr-date-range-picker carries "range" and "picker". And the /testing entry points are in the same catalog, so a harness can surface next to the component it drives; they are not a separate index.
get_ngwr_component
One entry point, by name, symbol or selector.
{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_ngwr_component","arguments":{"name":"WrSelect"}}}# ngwr/select
Native-like select built on CDK Overlay. A signal-forms native control — it implements `FormValueControl`, so `[formField]` binds straight to its `value` model. `[(value)]` works standalone, and `[(ngModel)]` / reactive forms keep working through Angular's bridge.
- selector: wr-option-group, wr-option, wr-select
- import: import { WrSelect } from 'ngwr/select'
- styles: @use 'ngwr/select';
- exports: WrSelect, WrOption, WrOptionGroup, WR_SELECT, WrSelectContext, WrSelectMode, WrSelectSearchLoader, WrSelectTagValidator, WrSelectSize
- classes with an API: WrSelect, WrOption, WrOptionGroup (use get_ngwr_api)
- docs: https://ngwr.dev/reference/components/select
- docs as markdown: https://ngwr.dev/reference/components/select.md The last two lines are the hand-off. This tool is deliberately a summary — when an agent needs the prose, the demos and the API tables, the markdown twin of the docs page is a better answer than anything the server could re-state, and it is one fetch away.
get_ngwr_api
The published signature, read out of the shipped declarations.
{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"get_ngwr_api","arguments":{"symbol":"WrAlert"}}}# WrAlert — ngwr/alert
Inline status banner. Use for feedback messages — saved/failed/notice etc.
## inputs
- `closeLabel`: string | null — Accessible name. Falls back to `alert.close`, then `'Close alert'`.
- `title`: string | null default null — Optional headline shown at the top of the alert.
- `type`: WrAlertType default 'info' — Visual variant.
- `iconName`: string | null default null — Override the default per-type icon with any ngwr icon name.
- `message`: string | null default null — Optional secondary message rendered below the title.
- `icon`: boolean default true — When `true`, renders a leading status icon matching the `type`. Pass `false` to hide. Ignored when `iconName` is set.
- `closeable`: boolean default false — When `true`, renders a close button.
## outputs
- `closed`: void — Emitted when the user dismisses the alert via the close button.
## Example
```html
<wr-alert title="Saved" message="Your changes are live." type="success" />
<wr-alert title="Failed" type="danger" closeable (closed)="onClose()" />
```
Docs: https://ngwr.dev/reference/components/alert Required-ness and the template alias come from the ɵcmp declaration rather than from the member's type, which is the only place either is actually written down: input() and input.required() both emit InputSignal<T>. Signal wrappers are unwrapped, so a type reads as the thing a template binds. Pass kind to narrow — "kind": "input" on a component the size of wr-table is the difference between a page of output and the part you asked for.
get_ngwr_setup
Everything you have to run, as text.
{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"get_ngwr_setup","arguments":{"symbols":["WrSelect","WrDatePicker"]}}}# Setting these up
## 1. Install
ng add ngwr
(prompts for styles, date adapter, density and theme, and prints a bootstrap snippet)
## 2. Wire each symbol into the component that uses it
ng g ngwr:use WrSelect --path src/app/some.component.ts # ngwr/select
ng g ngwr:use WrDatePicker --path src/app/some.component.ts # ngwr/date-picker
`--path` is a NAMED option, not positional — passing it bare fails with `Unknown argument`.
## 3. Styles
@use 'ngwr'; // everything, or per component:
@use 'ngwr/select';
@use 'ngwr/date-picker';
## 4. Providers these need
provideWrOverlay() // from 'ngwr/overlay'
why: overlays render into an ngwr-owned container; without it they never appear
provideWrDateAdapter(wrDateFnsAdapter) // from 'ngwr/date-adapter-fns'
why: every date mode goes through an adapter; there is no built-in default Section 4 only appears for symbols that need it, and the list behind it is short on purpose: these are the providers a component cannot be made to work without. WrDateAdapter is the clearest one — it is an abstract class with no root fallback, so a date picker without provideWrDateAdapter() fails at injection rather than rendering something almost right.
What it will not do
Worth knowing before you wire it into an agent loop.
It returns commands; it never runs them.get_ngwr_setup hands back the ng add and ng g ngwr:use lines as text. Running them is your agent's decision and your approval, through whatever your client already asks you for.
It never reaches outside its own package. No network, no commands, and the only files it opens are four inside its own installed package: llms-full.txt, schematics/use/symbol-map.json, types/*.d.ts, and its own package.json — for the version it reports, and for the exports map that decides whether an entry point gets a @use line at all (the harnesses, the services and the adapters ship no stylesheet, and telling a consumer to @use one would break their Sass build). It never looks at your source, never writes anything, and makes no network calls — so it cannot tell you which ngwr components your app already uses, and cannot fetch a docs page for you. It hands out the URL and lets the agent fetch it.
It answers for the version it was launched from. Under npx that is the latest published release, not necessarily yours — see the pinning section above.
A tool that THROWS answers with isError: true and a sentence saying what went wrong, rather than a JSON-RPC error — a failed call should leave the conversation intact and tell the agent enough to retry. A malformed REQUEST is the other way round: an unknown tool name or a wrong argument type comes back as -32602, because the request itself is the problem. A misspelled component name is neither — it is an ordinary successful answer whose text suggests a search, which is why the example above carries no error field at all.
Driving it by hand
Useful when a client says only that the server failed.
# Newline-delimited JSON on stdin, one message per line. No client and no
# SDK are involved, which makes a misbehaving setup easy to bisect.
echo '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"get_ngwr_component","arguments":{"name":"wr-datepicker"}}}' \
| node ./node_modules/ngwr/mcp/server.js
# The answer, verbatim. The text an agent reads is content[0].text:
{"jsonrpc":"2.0","id":7,"result":{"content":[{"type":"text","text":"No ngwr entry point matches \"wr-datepicker\". Use search_ngwr to find one."}]}} Stdout carries the protocol and nothing else, which is why diagnostics go to stderr: one stray console.log in that process corrupts the stream and the client drops the connection. If you fork the server or wrap it, keep that rule.
It ships no second copy of the catalog
The design constraint the server was built under: it adds no new source of truth. Every answer above is read at request time out of files the tarball already contains for other reasons — llms-full.txt is generated from library source on every build, schematics/use/symbol-map.json is generated from a public-api scan for the schematics, and the declarations are what tsc emitted for your editor.
That is the whole reason to trust it. An embedded index would be a fourth copy of the catalog to keep in step with the library, and — being the copy nobody's build fails over — the first one to go stale. What the server adds is not data. It is the ability to ask.
It is hand-rolled on the protocol rather than built on the MCP SDK for the same reason the library has one runtime dependency: an SDK would put a dependency tree behind every npm i ngwr for a feature most consumers never run. A tools-only server is four methods and a framing rule.