@dreamtree-org/twreact-ui 1.1.50 → 1.1.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # dreamtree-ui MCP server
2
+
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes
4
+ `@dreamtree-org/twreact-ui` to AI agents: a coding agent can **discover the
5
+ catalog, fetch authoritative prop contracts, search by capability, and read the
6
+ docs — live**, instead of guessing prop names or copy-pasting from memory.
7
+
8
+ This is the concrete delivery of the project's **AI-native moat** (roadmap issue
9
+ [#61](https://github.com/DreamtreeTech/dreamtree-ui/issues/61)): something a
10
+ copy-paste generator like shadcn/ui structurally cannot offer.
11
+
12
+ ## What it serves
13
+
14
+ **Tools**
15
+ | Tool | Args | Returns |
16
+ | --- | --- | --- |
17
+ | `list_components` | — | The full grouped catalog (core / feedback / navigation / utility + hooks + utils) with descriptions. |
18
+ | `get_component` | `name` | One component's spec: import line, props (name/type/default/description), variants, sizes, examples, family exports, a11y + convention notes. Accepts a family-export name (`useToast` → `Toast`). |
19
+ | `search_components` | `query` | Ranked matches across names, family exports, descriptions, prop names, and examples. |
20
+
21
+ **Resources**
22
+ - `dreamtree://skill` — the consumer AI usage guide (`ai-skills/dreamtree-ui.md`).
23
+ - `dreamtree://docs/<Component>` — the per-component manual (`doc/<Component>.md`), one per documented component.
24
+
25
+ **Prompt**
26
+ - `compose_ui` (optional `task` arg) — primes an agent to build UI with the library correctly (vocabulary, providers, the get_component-before-use rule).
27
+
28
+ ## Single source of truth
29
+
30
+ The catalog (`catalog.mjs`) is **derived, not hand-maintained**: it reads the
31
+ public exports from `src/index.js` (names + group via import path) and the
32
+ props/examples from `doc/<Component>.md`. Keep those accurate and the MCP server
33
+ stays truthful automatically. There is no duplicate component list to drift.
34
+
35
+ ## Run it
36
+
37
+ ```bash
38
+ # from the repo root
39
+ npm run mcp # node mcp/server.mjs (stdio transport)
40
+ npm run mcp:smoke # end-to-end check: boots the server, exercises every tool/resource/prompt
41
+ ```
42
+
43
+ It speaks MCP over **stdio** — logs go to stderr, the protocol uses stdout.
44
+
45
+ ## Use it from Claude Code (this repo)
46
+
47
+ Already wired in [`.mcp.json`](../.mcp.json):
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "dreamtree-ui": { "command": "node", "args": ["mcp/server.mjs"] }
53
+ }
54
+ }
55
+ ```
56
+
57
+ Open this repo in Claude Code and the `dreamtree-ui` tools/resources are
58
+ available. The companion Claude Code skill at
59
+ `.claude/skills/dreamtree-ui/SKILL.md` tells the agent to query these tools
60
+ before composing UI.
61
+
62
+ ## Use it from another MCP client
63
+
64
+ Point any MCP client at `node /path/to/dreamtree-ui/mcp/server.mjs`. It needs
65
+ this repo present (the catalog reads `src/index.js` + `doc/`).
66
+
67
+ ## Repo-local vs shipped — both in this package
68
+
69
+ - **Live server (`mcp/server.mjs`)** — reads `src/index.js` + `doc/*.md` live
70
+ via `mcp/catalog.mjs`. It's what `.mcp.json` and `npm run mcp` run.
71
+ - **Shipped server** — consumers run `npx -y @dreamtree-org/twreact-ui mcp`
72
+ (the `mcp` subcommand of the library's bin, `bin/cli.mjs`). Since `src/` and
73
+ `doc/` aren't in the published tarball, it reads a frozen build-time
74
+ **snapshot** (`mcp/catalog.snapshot.json`) via `mcp/catalog-snapshot.mjs`.
75
+
76
+ Both import the same handlers (`server-core.mjs`) and pure queries
77
+ (`catalog-core.mjs`) — single source of truth, nothing copied.
78
+ `npm run mcp:snapshot` regenerates the one generated artifact,
79
+ `mcp/catalog.snapshot.json`. The MCP SDK is a runtime `dependency` of the
80
+ library, lazy-imported only by the `mcp` subcommand (never in a consumer's app
81
+ bundle). See
82
+ [`docs/agents/11-mcp-sync.md §7`](../docs/agents/11-mcp-sync.md#7-consumer-distribution--bundled-in-the-main-package)
83
+ (delivered under #74 / the moat in #61).
@@ -0,0 +1,44 @@
1
+ // Pure catalog queries — no filesystem, no data source.
2
+ // ---------------------------------------------------------------------------
3
+ // These operate on an already-built catalog object (the shape returned by
4
+ // buildCatalog()), so they are identical whether the catalog came from the
5
+ // live source (mcp/catalog.mjs, reads src/ + doc/) or the frozen snapshot
6
+ // (mcp/catalog-snapshot.mjs, reads mcp/catalog.snapshot.json). Both servers
7
+ // import this file directly — no copying — which is what lets the repo-local
8
+ // `mcp/server.mjs` and the shipped `twreact-ui mcp` subcommand share one
9
+ // implementation.
10
+
11
+ function allEntries(catalog) {
12
+ return [...catalog.components, ...catalog.hooks, ...catalog.utils, ...catalog.store];
13
+ }
14
+
15
+ // Resolve a name to a single catalog entry: exact match, then family-export
16
+ // (e.g. "useToast" → "Toast"), then case-insensitive. Returns null if none.
17
+ export function findComponentIn(catalog, name) {
18
+ const all = allEntries(catalog);
19
+ const lower = String(name || "").toLowerCase();
20
+ return (
21
+ all.find((x) => x.name === name) ||
22
+ all.find((x) => x.familyExports.includes(name)) ||
23
+ all.find((x) => x.name.toLowerCase() === lower) ||
24
+ null
25
+ );
26
+ }
27
+
28
+ // Ranked keyword search across names, family exports, descriptions, prop
29
+ // names, and examples. Returns entries with a `_score`, highest first.
30
+ export function searchCatalogIn(catalog, query) {
31
+ const q = String(query || "").toLowerCase().trim();
32
+ if (!q) return [];
33
+ const scored = [];
34
+ for (const x of allEntries(catalog)) {
35
+ let score = 0;
36
+ if (x.name.toLowerCase().includes(q)) score += 10;
37
+ if (x.familyExports.some((f) => f.toLowerCase().includes(q))) score += 6;
38
+ if (x.description.toLowerCase().includes(q)) score += 3;
39
+ if (x.props.some((p) => p.prop.toLowerCase().includes(q))) score += 2;
40
+ if (x.examples.toLowerCase().includes(q)) score += 1;
41
+ if (score > 0) scored.push({ ...x, _score: score });
42
+ }
43
+ return scored.sort((a, b) => b._score - a._score);
44
+ }
@@ -0,0 +1,42 @@
1
+ // Snapshot-backed catalog — what the SHIPPED `twreact-ui mcp` server reads.
2
+ // ---------------------------------------------------------------------------
3
+ // Exposes the SAME surface as the live mcp/catalog.mjs
4
+ // buildCatalog, findComponent, searchCatalog, listDocNames, readDoc, readSkill
5
+ // so the shared server core (mcp/server-core.mjs) is agnostic to the source.
6
+ // Here the data is a frozen snapshot (mcp/catalog.snapshot.json, generated by
7
+ // scripts/gen-mcp-snapshot.mjs) — no src/ or doc/ needed at runtime, so it
8
+ // works after a plain `npm install @dreamtree-org/twreact-ui`. The pure query
9
+ // helpers are shared verbatim with the live server (./catalog-core.mjs); no
10
+ // copying — both the live and shipped servers import the same files in this
11
+ // package.
12
+ import { readFileSync } from "node:fs";
13
+ import { fileURLToPath } from "node:url";
14
+ import { dirname, join } from "node:path";
15
+ import { findComponentIn, searchCatalogIn } from "./catalog-core.mjs";
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const snapshot = JSON.parse(readFileSync(join(__dirname, "catalog.snapshot.json"), "utf8"));
19
+
20
+ export function buildCatalog() {
21
+ return snapshot.catalog;
22
+ }
23
+
24
+ export function findComponent(name) {
25
+ return findComponentIn(snapshot.catalog, name);
26
+ }
27
+
28
+ export function searchCatalog(query) {
29
+ return searchCatalogIn(snapshot.catalog, query);
30
+ }
31
+
32
+ export function listDocNames() {
33
+ return Object.keys(snapshot.docs);
34
+ }
35
+
36
+ export function readDoc(name) {
37
+ return snapshot.docs[name] ?? null;
38
+ }
39
+
40
+ export function readSkill() {
41
+ return snapshot.skill ?? null;
42
+ }