# Astryx: what Meta's design system tells an agent, and how its search ranks

> Satyajit Ghana — Head of Engineering @ Inkers Technology
> canonical: https://ai.satyajit.si/articles/astryx
> date: 2026-11-09
> tags: agents, mcp, developer-tools, generative-ui, agentic-coding

Two posts on 8 November said Meta had open-sourced Astryx, "a design system built for people and the AI agents working with them". One listed React 19 and StyleX, docs, Storybook and a sandbox, an MIT licence and a beta label. The other, in Chinese, added the bigger numbers: eight years of internal use, more than 13,000 apps, 150-plus components, and a CLI and an MCP server through which an agent can read component docs and templates.

The news part is stale. Astryx's own blog dates the launch to 18 June 2026, and a "one month" post from July reports 10.3K GitHub stars by then. What is still worth an article is the part the posts gesture at and nobody shows: what does a design system "for agents" actually hand an agent, and is the answer any good? So I cloned the repository at its newest commit, counted what it ships, read the agent surfaces, and queried the hosted MCP server directly. On a first pass the sandbox's permission layer refused to execute the CLI's npm package, and this article said so. With that permission granted, on 10 November I installed the published CLI, Core and the neutral theme at 0.7.11 into a scratch project and ran it: the same 24 searches, every component read at every detail level, `init` both ways, and Core pinned to an older release. Those results are in [the CLI section](#the-cli-run-against-the-same-questions) and are marked measured.

<RepoCard repo="meta/astryx" note="Read at da85f28 (9 November 2026, the depth-1 clone's only commit). MIT. Published @astryxdesign/core and @astryxdesign/cli on npm at 0.7.11; the repo's CLI package.json says 0.7.12." />

## What is in the repository

The repo is a pnpm monorepo with three top-level directories: `packages/` (what is published), `apps/` (the docsite, Storybook, a sandbox, a template viewer and six example apps for Next.js and Vite, with and without Tailwind), and `internal/` (test utilities, an ESLint plugin, and the "vibe tests" I come back to). `LICENSE` is MIT, copyright 2026 Meta Platforms. The root `package.json` declares the same.

The published surface is four kinds of package:

- `@astryxdesign/core`: the components, the theme system and utilities. Its `package.json` declares 172 export paths, one per component module plus CSS and token entry points.
- `@astryxdesign/cli`: docs, search, templates, scaffolding, themes and codemods.
- `@astryxdesign/build`: plugins for consumers who compile StyleX from source instead of importing the pre-built CSS.
- Seven themes: neutral, butter, chocolate, matcha, stone, gothic and y2k. An eighth directory, `probe`, is a test theme.

Three more packages exist but are not stable: `lab` (experimental components, used by Storybook and not published), and `charts` and `vega`, which the README says ship only under the `@canary` dist-tag.

### Counting the 150+

Every component documents itself in a `.doc.mjs` file next to its source, typed as a `ComponentDoc` or a `HookDoc` from the CLI's authoring contract. That makes the count checkable without running anything. In `packages/core/src` (measured):

| What | Count |
|---|---|
| `ComponentDoc` files | 198 |
| `HookDoc` files | 63 |
| directories holding them | 104 |
| `ComponentDoc` files in `lab`, `charts`, `richtext` | 44, 6, 4 |
| test files under `core/src` | 812 |

The 198 include sub-components with their own docs (`DialogHeader`, `TableCell`, `TypeaheadItem`), and the 104 directories are closer to what a person would call a component. Either way "150+ components" holds for Core alone if you count documented parts, and it would hold for directories if you added `lab`. The README's own phrase is "150+ accessible components". I would read it as documented parts.

One small tell that the number moves: the CLI's agent-docs generator counts components live from the installed Core, and when it cannot, it falls back to a hard-coded string, `'90+'` (`packages/cli/foundation/agent-docs/agent-docs.mjs:620`). An agent in a project where discovery fails is told about 90.

### The 13,000 apps and eight years

These are reported, and only by Meta. The README says Astryx "grew inside Meta over the last eight years" and powers "13,000+ apps". The docsite blog says "13,000+ products" in one post and "over 13,000 apps" in another. Nothing in the repository can check either number, and nothing tries. The git history starts fresh: my clone is one commit deep, and the copyright line says 2026. I treat both as Meta's description of the internal system this was rebuilt from, which is what the blog post on how it works says it is: "that internal system rebuilt on the open-source stack".

### Theming in one paragraph

A theme is a flat map of CSS custom properties, each either one value or a `[light, dark]` pair that becomes `light-dark()`. `defineTheme` in `core/src/theme/defineTheme.ts` takes that map plus optional icon overrides; the theme either injects a `<style>` tag at runtime or is pre-compiled by `astryx theme build` into a CSS file selected by a `data-astryx-theme` attribute. Core's Tailwind bridge, `tailwind-theme.css`, names 218 distinct custom properties (measured with a grep), which is a fair proxy for the token surface a theme can touch. The colour code includes an HCT implementation (`theme/hct.ts`), the perceptual space Material uses, for expanding one accent into a scale. Consumers never see StyleX: they import pre-built CSS and override with `className` in Tailwind, CSS modules or plain CSS.

## Three ways an agent learns it

The README's claim is that "the API, docs, and CLI are designed together so a person and an AI assistant build the same way." Reading the CLI package and the docsite, an agent can learn Astryx through three different channels, and they differ in one way that matters: which version of the docs answers.

<AgentChannels />

**The init block.** `astryx init` writes a managed block, fenced by `<!-- ASTRYX:START -->`, into `AGENTS.md`, and a one-line import of `AGENTS.md` into `GEMINI.md`. It writes the same block into `CLAUDE.md` only when the project already has one (measured below; `--agent claude` asks for it explicitly). The block is short on purpose: about 2,100 characters. It states the version and component count, the CLI prefix to use, the CSS imports without which every component renders unstyled, a template-first workflow, a handful of house rules (no raw `<div>`, tokens instead of hex), and the commands and docs topics an agent should know to ask for; the generator's comment says why: "An agent cannot ask for a topic it was never told about". With `--user`, init instead writes one skill file to `~/.agents/skills/astryx/SKILL.md` and links it into `~/.claude/skills` and, where those folders exist, the skills folders of Kiro, Windsurf, Letta and Xcode's assistant, so agents anywhere on the machine know Astryx exists.

**The CLI.** Sixteen commands, every one with `--json`, which returns a typed envelope: `{ apiVersion, type, data, meta }`. `meta.answeredBy` names the CLI version, the Core version it paired with, and which copy answered: every read I ran in the scratch project came back `{ cli: "0.7.11", core: "0.7.11", copy: "project" }` (measured), and `init --user`, run outside any project, came back `copy: "carried"`. `--detail brief|compact|full` and `--dense` trade completeness for tokens; `--zh` gives Simplified Chinese where a translation exists. Each component's doc file carries all three: `docs`, `docsZh` and `docsDense` live in the same `.doc.mjs`. The important property is that the project's own install answers, so an agent reading `astryx component Button` gets the props of the Button the project actually has.

**The MCP server.** A Next.js route on the docsite, `apps/docsite/src/app/mcp/route.ts`, served over Streamable HTTP at `https://astryx.atmeta.com/mcp`. No install. Two tools, `search` and `get`. It answers from the docsite's generated registries, so it describes whatever the docsite last deployed, not the version in your lockfile. Its `get` responses do include an `isReady` flag and the exact install command (`@canary` for components that are not ready), which is the server's way of telling an agent that it may be describing something your version does not have.

That last difference is the one I would weigh. The CLI is versioned to the project and the MCP server is not. A team pinned to an older Core gets current docs from MCP and correct docs from the CLI; I [pinned one and checked](#the-version-gap-measured). The Astryx team clearly thought about this: one of the decisions its blog lists as settled by a vibe test is "CLI vs MCP", and the CLI is the channel `init` points agents at.

## What the MCP server returns

The server was the first surface I exercised end to end. I sent `initialize`, `tools/list`, and then 84 tool calls from a short Python client on 9 November 2026 (measured; the response headers say Vercel, `serverInfo` says `astryx 2.0.0`). Every call I timed returned in under a third of a second; the median search took 0.20 s.

`tools/list` confirms the two tools. `search` takes `query` and an optional `limit` (default 8). `get` takes `name` and an optional `section` for long doc topics.

### How search ranks

The scoring is about a hundred lines and worth reading in full, because it explains everything I measured. For each component it takes the best of four numbers:

- `score(name, query)` and `score(description, query)`: 100 for an exact match, 90 if the text starts with the query, 85 if it contains it, then 75, 50 or 30 if all, at least half, or some of the query's words appear.
- the same score against each keyword in the doc file.
- a flat **90** if the component appears in the keyword index for this query.

Docs and templates are scored the same way without the keyword boost. Sub-components lose one point so a parent outranks its parts. The list is sorted by score, highest first, and cut to `limit`.

The keyword index is the interesting part (`keywordIndex.ts`). It maps each lowercased keyword to the components that list it, and a keyword "resolves" for a query when **the query contains the keyword or the keyword contains the query**. Two consequences follow directly from that rule:

1. **Everything that resolves ties at 90.** A component with one matching keyword and a component with three get the same score. JavaScript's sort is stable, so the tie is broken by registry order, which in every tied run I looked at was alphabetical.
2. **Short keywords match inside words.** A keyword `as` resolves for "toast notification", because "toast" contains "as". `ai` resolves for "breadcrumb trail", `ul` for "multi step form wizard", `toc` for "autocomplete search box", `pan` for "side panel".

Neither is a guess about the hosted behaviour. I re-implemented only the keyword stage from the repository's own `keywordIndex.ts`, ran it over the keyword lists parsed from Core's doc files (173 documented components and hooks, 1,599 keywords), and compared: of the 121 results the server put in its top eight because of a keyword, my re-implementation placed 120 in the same tied band (measured). The one miss is `illustrations`, an entry defined in the CLI package rather than in Core.

### Twenty-four queries

The Astryx team's own rule for vibe-test prompts is that they "should describe the _user experience_ rather than specific components". I wrote 24 queries that way (a toast notification, a side panel that slides in, confirm before deleting, an on off toggle) and named, before sending, the component a person who knows the library would want. Then I recorded where the server put it.

<SearchProbe />

| Expected component in… | Hosted server | Same lists, whole-word tie-break |
|---|---|---|
| first place | 11 of 24 | 16 of 24 |
| top three | 15 of 24 | 20 of 24 |
| top eight (the default) | 22 of 24 | 22 of 24 |

The first column is measured. The second is reasoned: I took the eight results the server returned, kept its order outside the keyword band, and inside the band sorted by how many keywords matched the query as whole words. It is not a server run, and it cannot rescue a component the server never returned.

A few cases carry the pattern:

- **"dropdown menu"** returns `ContextMenu` first and `DropdownMenu` second. Both resolve through the keyword index, both score 90, and C sorts before D. `DropdownMenu`'s own name scores only 75 against the query, because the name has no space.
- **"toast notification"** puts `Toast` fifth, behind `Badge`, `Banner`, `Item` and `MediaTheme`. Each of those lists `notification` or `toast` as a keyword. `Toast` lists both, and gets nothing for it. Counting whole-word hits would move it to second, behind `Banner`, which also lists both.
- **"date picker"** never returns `DateInput` in the first eight. It is 11th. `Calendar` lists the keyword `date picker` and comes first, which is a fair answer for a month grid and the wrong one for a form field.
- **"autocomplete search box"** puts `Typeahead` tenth, behind `Card` and `Layout` (keyword `box`) and `Outline` (keyword `toc`, inside "autocomplete").

The server is not bad at this. When the query names the thing ("command palette", "user avatar", "breadcrumb trail", "empty state"), the right component comes first every time. It is weakest exactly where the vibe-test rule says agents live: describing the experience instead of naming the component. And an agent does not see scores, only an order. Most agents take the first plausible hit and call `get` on it.

Two small fixes would move most of these: count keyword hits instead of flagging them, and match keywords on word boundaries. Both are a few lines in `keywordIndex.ts`. The CLI's `astryx search` is a different implementation that does roughly both, and it is [compared below](#the-cli-run-against-the-same-questions) on the same 24 queries.

### What one answer costs

The other thing an agent pays is context. A brief search result carries the import line, the install command, up to six key props, related components and a hint to call `get`. A `get` returns everything: props with types and descriptions, usage guidance, theming notes, related hooks, and two code examples.

<ContextBudget />

Measured over 24 components: a `get` response is 5,375 characters for `Spinner` at the small end and 17,753 for `Selector` at the large end, median about 10,300 characters, or roughly 2,600 tokens at four characters a token (the token figure is an estimate; I did not run a tokenizer). A search answer at the default limit is a median of about 5,400 characters. A page that needs a table, a dialog, a selector and a toast pulls about 58,400 characters of reference, around 14,600 tokens, once.

That is the design choice worth noticing, and it is the opposite of the one on this site's [json-render + Jev](/articles/generative-ui-by-decision) piece. There, the whole component catalog travels with every model call, about 7,100 tokens each time, because a decision model picks from options your code enumerated. Astryx sends nothing until asked, then sends a lot about one thing. Retrieval is cheaper when an agent builds a page from a handful of components; it is only as good as the search that decides which handful.

## The CLI, run against the same questions

Everything in this section is measured on 10 November 2026: `npm install` of `@astryxdesign/cli`, `core` and `theme-neutral` at 0.7.11 (the `latest` tag that day) into an empty scratch project, every command run through `npx astryx` with `HOME` pointed at a scratch folder so nothing touched a real home directory.

### How the CLI ranks

The CLI does not share the server's scorer. Its search lives in about 4,400 lines under `api/search/` in the installed package: a lexical ranker that scores names, keywords, titles, synonyms and plurals, and gives each result a reason it prints ("exact name", "plural of the name", "matches 2/2 terms: toast, notification"). The score falls into one of eight bands, with floors at 195, 188, 170, 160, 151, 100 and 55 and one more below that (`_order.mjs`), and a learned order, shipped as weights files beside the code, rearranges results inside a band but never across one. Default limit is 20, not 8.

Two of the server's problems are gone by construction. Matching is on words, so short keywords no longer match inside query words: `useInteractiveRole`, which the server returned for "toast notification" because of `as`, and `Outline`, returned for "autocomplete search box" because of `toc`, appear nowhere in the CLI's full result lists for those queries. And a result that covers every word of the query scores in the top tier, above one that covers half of it, which is close to the "count the hits" fix proposed above.

<SearchProbe start="cli" />

| Expected component in… | Hosted MCP (9 Nov) | CLI, all domains | CLI, `--type component` |
|---|---|---|---|
| first place | 11 of 24 | 15 of 24 | 17 of 24 |
| top three | 15 of 24 | 21 of 24 | 22 of 24 |
| top eight | 22 of 24 | 22 of 24 | 23 of 24 |

Against the server, the CLI moved the expected component up on ten queries, down on four, and left ten where they were. "Autocomplete search box" goes from tenth to first, "tooltip on hover" from seventh to first, "on off toggle" from sixth to first, "date picker" from 11th to second. Each of those was a server failure caused by an alphabetical tie or an infix keyword.

It does not remove ties. When two results score the same, the CLI keeps them in name order too: "modal dialog" puts four components at 159 (`AlertDialog`, `BottomSheet`, `CommandPalette`, `Dialog`) and `Dialog` is fourth (fifth on the server); "toast notification" has `Banner`, `Toast` and `useToast` tied at 159, so `Toast` is second.

And it has a failure of its own, from the learned order inside a wide band:

- **"tabs"** puts `Tab` first ("plural of the name", 99), which is defensible: its import path is `@astryxdesign/core/TabList`. But then come ten templates that "render Tab" at 60, and only then `TabList` at 90, in 13th place. The band runs from 55 to 99, so a 60 can lead a 90. The server had `TabList` second.
- **"paginate a long table"** puts `Table` (105) first and `Pagination` (112) third.
- **"chat interface"** leads with a hook and a run of chat templates at 98, and `ChatComposer` is 64th at 58. With `--type component` the first answer is `ChatLayout`, which is a fair answer to that query; my expected pick may be the weaker one there.

So the CLI's search is better on the queries this article was written to test, and `--type component` is worth passing when an agent wants a component rather than a template. It is still a ranking an agent sees only as an order.

Speed is not a reason to prefer either. A search through `npx` took 1.5 to 2.1 seconds of wall time here, almost all of it Node starting up on a shared, deprioritised CPU; the server answered in a median 0.20 s over the network.

### What a CLI read costs

The size measurements, for the same 24 components the server was measured on (all measured, characters of stdout):

| Read | Median | Smallest | Largest |
|---|---|---|---|
| MCP `get` (9 Nov) | 10,322 | 5,375 | 17,753 |
| `component <Name> --json`, any `--detail` | 10,032 | 5,254 | 19,790 |
| `component <Name>`, text, `--detail full` | 6,642 | 3,286 | 13,399 |
| text, `--detail compact` | 5,196 | 1,546 | 10,625 |
| `--dense` | 5,475 | 2,794 | 13,051 |
| `--props --json` | 3,938 | 1,251 | 10,107 |
| text, `--detail brief` | 822 | 261 | 1,347 |

The `--json` reads were byte-identical at `brief`, `compact` and `full`, for every one of the 24. That matches the CLI's own type file, where `--detail` shapes the JSON of a component *list* and the JSON of a single component has one shape. An agent that asks for JSON pays for full detail whatever it passes, about the same as the server's `get`. The cheap reads are text: `--detail brief` is a signature line, one line of description and the main props in a median of 822 characters, about a twelfth of the full read. The table, dialog, selector and toast page from earlier costs 51,076 characters as CLI JSON and 3,310 as brief text. The context-cost chart above now has a switch for each of these.

Search answers are about the size of the server's at the same limit: `--json --limit 8` has a median of 5,219 characters, against about 5,400 from the server. At the CLI's default limit of 20 the median is 12,272.

### What init actually writes

In an empty project with a `package.json` and the three packages installed, `astryx init` wrote two files: `AGENTS.md`, holding a heading plus the managed block, and `GEMINI.md`, holding the block markers around one line, `@./AGENTS.md` (55 bytes). It did not create `CLAUDE.md`. In a second project that already had a `CLAUDE.md`, it wrote the block into that too, byte-identical to the one in `AGENTS.md`, below the existing notes.

The block is 2,136 characters, 317 words. Its first line is `Astryx v0.7.11 · 174 components`, counted live from the installed Core, not the `'90+'` fallback. Then the `npx astryx` prefix, the two CSS imports, a three-step workflow that starts every page from a template (`astryx build "<idea>"`, then `astryx template`), a rule to run `astryx doctor --built <dir>` before delivering, six rules (no `<div>`, read `astryx docs layout` before changing a template's frame, dense data as rows, props first and then tokens with no raw hex or px, palettes through the theme, and a self-check), and the key commands. The first version of this article, written from source, undersold it: it is a short operating manual, not only a pointer to the docs.

`astryx init --user` wrote one 1,880-byte `SKILL.md` under the scratch home's `.agents/skills/astryx/`, made `.claude/skills/astryx` a relative symlink to it, and recorded that link in a `settings.json` so that removal undoes exactly what it did. The skill tells an agent to start with `astryx search`, to read `astryx component` rather than the package's files, and that "in a project, the project's version of Astryx answers automatically".

### The version gap, measured

The argument above, that the CLI answers for the installed version and the server does not, is now measured. Core has 3,266 versions on npm, 47 of them outside the canary and test tags. I installed CLI 0.7.11 beside Core 0.5.0 in a second scratch project.

- `answeredBy` came back `{ cli: "0.7.11", core: "0.5.0", copy: "project" }`: the current CLI reads the old Core's docs.
- The component list has 167 names at 0.5.0 and 174 at 0.7.11. Seven are new, among them `Drawer`, `InfoTip` and `Timer`; none were removed.
- `astryx component Drawer` at 0.5.0 exits with `ERR_UNKNOWN_COMPONENT` and suggests `MobileNav` and `SideNav` instead. The hosted server's `get("Drawer")`, called the same day, returns its full docs with `isReady: true` and the install line `npm install @astryxdesign/core`, with no version in it. `Drawer` is the answer both searches give for "side panel that slides in".
- Of the 23 probe components that exist in both, 11 have a different prop list. `Selector` has 26 props at 0.5.0 and 38 now, and lost one: `xstyle`, which the server's current `Selector` does not list. `Typeahead`'s `emptySearchResultsText` became `emptySearchText`.

An agent in that project that trusted the server would import a component the project does not have, and miss a prop it does. One that ran the CLI would be told the truth about its own install.

## How Astryx measures itself

The most unusual thing in the repository is not a component. It is `internal/vibe-tests/`: a harness that gives the same prompt battery to fresh agents building with Astryx, with Astryx plus Tailwind, with shadcn and Tailwind, and with raw HTML, and has a separate judge agent score the results. The blog post describing it reports (these are Meta's numbers; I did not run the harness, which needs agent API access):

- On a good night, 2026-07-10: Astryx 98 overall, Astryx with Tailwind 97, the shadcn and Tailwind baseline 95, raw HTML 82.
- On a bad night, 2026-06-28: Astryx 77, losing to the baseline, because correctness fell to 2 when "the agents hallucinated imports and prop names on a handful of prompts".
- And, unusually candid: "Across our full ledger the baseline actually wins the plurality of nights".

A nightly job runs the battery, and a second agent, the "vibe test debugger", reads failures and opens pull requests against the docs or CLI. A proposal in the same directory, `cli-discovery-test/PLAN.md` (status: "proposal / design (not yet implemented)"), asks the upstream question this article circles: when an agent lands in a project that installed Core but never ran `init`, does it find the CLI at all? Its hypotheses rank a `@see` banner in the type definitions above a README callout, and a postinstall banner near the bottom, since pnpm skips dependency scripts.

I like that this exists more than I like any single number in it. Most design systems that say "AI-ready" mean they wrote a doc page. This one runs a nightly experiment against a baseline and publishes the nights it loses.

## Against the site's other agent-facing UI

This site has looked at three other answers to "how does an agent build a decent interface":

- [Impeccable](/articles/impeccable-design-skill) is a skill plus a linter: instructions about taste, and a detector that checks 59 rules on the page the agent produced. It constrains output after the fact.
- [json-render with Jev](/articles/generative-ui-by-decision) removes the model from layout: code enumerates the options, a decision model picks, and the catalog rides along on every call.
- Anatomy, the [isometric figure skill](/articles/anatomy-machine-skill), is a narrow house style for one kind of figure, written as a long prompt with verification scripts.

Astryx sits upstream of all three. It does not judge the output and it does not take the decision away. It gives the agent a real component library with a reference it can query, and bets that a well-documented system with predictable naming makes the agent's own guesses right more often. The vibe-test ledger is the evidence for that bet, and by its authors' account the bet is roughly even against shadcn and Tailwind, which models have seen far more of in training.

## What I ran and what I did not

**Ran, measured:** a shallow clone of `meta/astryx` at `da85f28`; counts of doc files, hook docs, directories and tests by grep; 84 MCP tool calls against the hosted server from Python's standard library; a re-implementation of the keyword stage over keyword lists parsed from Core's doc files (text parsing, nothing executed); an `npm install --ignore-scripts` of the published `@astryxdesign/cli`, `core` and `theme-neutral` at 0.7.11. On 10 November, with permission to execute the package: `astryx search --json` on the same 24 queries (at limits 8, 20 and 400, and with `--type component`); `astryx component` for 24 components as JSON at three detail levels, as text at three, `--dense` and `--props --json`; `astryx init` in two scratch projects and `init --user` with `HOME` redirected to a scratch folder; and CLI 0.7.11 against Core 0.5.0, compared with two fresh `get` calls to the hosted server.

**Refused or not attempted:** on the first pass the sandbox's permission layer refused to execute the CLI; that is resolved above, and nothing was worked around. Speed comparisons are rough: the CLI ran under `nice` on a shared CPU. I did not run the repo's own CLI tests (they need the monorepo installed, not the published package), its Vitest or Playwright suites, build Storybook, or run the vibe tests (they need agent API keys). I could not read the GitHub issue behind the "CLI vs MCP" decision; this session's GitHub access does not cover `meta/astryx`.

## Should you use it

If you are starting a React 19 app and you want agents to write most of the UI, Astryx is worth a trial. The licence is MIT, the component set is large, theming is plain CSS variables, and the agent channels are thought through further than any other design system I have read. Use the CLI, not the MCP server, as the agent's reference, and now that I have run both, for two measured reasons instead of one. It answers for the version you installed: pinned to Core 0.5.0, it said `Drawer` does not exist while the server described it as ready to install. And its search is better at the job agents actually do, describing an experience: first place 15 of 24 against the server's 11, top three 21 against 15. Run `astryx init` so the block lands in `AGENTS.md` (and create a `CLAUDE.md` first if you use Claude Code, or it will not get one). Two habits help the agent: pass `--type component` when it wants a component (17 of 24 first), and read with `--detail brief` text before the full doc, because JSON reads cost the full ~10,000 characters whatever `--detail` says.

If you do point an agent at the MCP server, give it names, not experiences. "Toast" works. "Toast notification" puts the toast fifth. Neither channel is free of rank surprises: the CLI puts `TabList` 13th for "tabs". And if you write your own keyword search for an agent to use, count the hits and match whole words: of the 24 queries here, that alone takes the right first answer from 11 to 16 on the server's own lists, and the CLI, which does roughly that, gets 15.

It is still beta. The `@canary` flags in `get` responses are there for a reason, and `charts` and `vega` are not on a stable tag yet.
