@lerpa/mcp-server 0.1.0

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/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # @lerpa/mcp-server
2
+
3
+ Model Context Protocol (MCP) server that exposes the Lerpa UI registry to AI coding agents (Claude Code, Cursor, v0, Continue, Cline, Zed).
4
+
5
+ ## Status
6
+
7
+ **Active** — `@modelcontextprotocol/sdk` is installed and `pnpm --filter @lerpa/mcp-server build` emits `dist/index.js`. Rebuild after editing `src/`:
8
+
9
+ ```bash
10
+ pnpm --filter @lerpa/mcp-server build
11
+ ```
12
+
13
+ ## What it exposes
14
+
15
+ Three read-only tools backed by `packages/registry/generated/manifest.json` and `apps/docs/src/data/component-catalog.json`:
16
+
17
+ | Tool | Description |
18
+ | --- | --- |
19
+ | `list_components(category?)` | Returns components filtered by category (`ai`, `buttons`, `cards`, `forms`, `creative`, `feedback`, `navigation`, `tables`, `calendars`, `ecommerce`, `dashboard`, `auth`, `account`, `docs`, `blog`). |
20
+ | `get_component(id)` | Returns the full registry item JSON for a single component, including embedded `files[].content` source code (copy-pasteable). |
21
+ | `search_components(query)` | Fuzzy search by name + description across all registry items. |
22
+
23
+ ## Configure in Claude Code
24
+
25
+ Build first, then register the server. Easiest is the CLI:
26
+
27
+ ```bash
28
+ pnpm --filter @lerpa/mcp-server build
29
+ claude mcp add lerpa --scope user -- node /absolute/path/to/packages/mcp-server/dist/index.js
30
+ ```
31
+
32
+ Or add a project-scoped `.mcp.json` at your repo root:
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "lerpa": {
38
+ "command": "node",
39
+ "args": ["/absolute/path/to/packages/mcp-server/dist/index.js"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Restart Claude Code; the three tools above become callable.
46
+
47
+ ## Configure in Cursor
48
+
49
+ `~/.cursor/mcp.json` — point at the built `dist/index.js` (works today, from inside this monorepo):
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "lerpa": {
55
+ "command": "node",
56
+ "args": ["/absolute/path/to/packages/mcp-server/dist/index.js"]
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ > The registry data is bundled into the package, so `npx -y @lerpa/mcp-server` works as soon as it is published to npm. Until the first `npm publish`, use the `node …/dist/index.js` form above.
63
+
64
+ ## Configure in Continue / Cline / Zed
65
+
66
+ Same shape — point `command`/`args` to `node /absolute/path/to/packages/mcp-server/dist/index.js` for local use (or the published `lerpa-mcp` binary once available).
67
+
68
+ ## Publishing
69
+
70
+ The package is self-contained. `pnpm --filter @lerpa/mcp-server build` runs `tsc` then `scripts/copy-registry.cjs`, which bundles `registry/registry.json` (the aggregated registry with embedded source) and `registry/component-catalog.json` into the package. At runtime `src/index.ts` reads those bundled files, falling back to monorepo paths during local dev. `files` ships `dist` + `registry`; `prepublishOnly` rebuilds the registry first; `publishConfig.access` is `public`.
71
+
72
+ To publish (verified self-contained outside the monorepo — `npm pack` tarball ≈ 1.3 MB):
73
+
74
+ ```bash
75
+ cd packages/mcp-server
76
+ npm publish # prepublishOnly rebuilds the registry + bundles, then publishes
77
+ ```
78
+
79
+ After the first publish, any MCP client can use `npx -y @lerpa/mcp-server`.
80
+
81
+ ## Data sources
82
+
83
+ Resolved bundled-first, with monorepo fallback for local development:
84
+
85
+ - Aggregated registry (names, types, embedded source): `registry/registry.json` ← `../registry/generated/registry.json`
86
+ - Component descriptions / categories: `registry/component-catalog.json` ← `../../apps/docs/src/data/component-catalog.json`
87
+
88
+ ## Development
89
+
90
+ ```bash
91
+ pnpm --filter @lerpa/mcp-server typecheck
92
+ pnpm --filter @lerpa/mcp-server build
93
+ node packages/mcp-server/dist/index.js # stdio MCP server
94
+ ```
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @lerpa/mcp-server
4
+ *
5
+ * Model Context Protocol server exposing the Lerpa UI registry to AI coding
6
+ * agents (Claude Code, Cursor, v0, Continue, Cline, Zed).
7
+ *
8
+ * Status: ACTIVE. `@modelcontextprotocol/sdk` is installed; `pnpm --filter
9
+ * @lerpa/mcp-server build` emits `dist/index.js`, runnable as a stdio MCP
10
+ * server by any MCP client (Claude Code, Cursor, Continue, Cline, Zed).
11
+ *
12
+ * Tools exposed (read-only):
13
+ * - list_components(category?) -> components filtered by category
14
+ * - get_component(id) -> full registry item JSON with source
15
+ * - search_components(query) -> fuzzy match on name + description
16
+ *
17
+ * Note: data is read from the monorepo registry (see paths below), so this
18
+ * runs as-is from inside the repo. Publishing to npm for `npx` use additionally
19
+ * requires bundling the registry JSON into the package — see README.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG"}
package/dist/index.js ADDED
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @lerpa/mcp-server
4
+ *
5
+ * Model Context Protocol server exposing the Lerpa UI registry to AI coding
6
+ * agents (Claude Code, Cursor, v0, Continue, Cline, Zed).
7
+ *
8
+ * Status: ACTIVE. `@modelcontextprotocol/sdk` is installed; `pnpm --filter
9
+ * @lerpa/mcp-server build` emits `dist/index.js`, runnable as a stdio MCP
10
+ * server by any MCP client (Claude Code, Cursor, Continue, Cline, Zed).
11
+ *
12
+ * Tools exposed (read-only):
13
+ * - list_components(category?) -> components filtered by category
14
+ * - get_component(id) -> full registry item JSON with source
15
+ * - search_components(query) -> fuzzy match on name + description
16
+ *
17
+ * Note: data is read from the monorepo registry (see paths below), so this
18
+ * runs as-is from inside the repo. Publishing to npm for `npx` use additionally
19
+ * requires bundling the registry JSON into the package — see README.
20
+ */
21
+ import * as fs from "node:fs";
22
+ import * as path from "node:path";
23
+ import { fileURLToPath } from "node:url";
24
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
25
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
26
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
27
+ // ---------------------------------------------------------------------------
28
+ // Resolve data sources relative to this file (ESM-safe).
29
+ // ---------------------------------------------------------------------------
30
+ const __filename = fileURLToPath(import.meta.url);
31
+ const __dirname = path.dirname(__filename);
32
+ // Data resolution: prefer the registry JSON bundled INTO this package by
33
+ // scripts/copy-registry.js (so the published package runs via `npx` outside the
34
+ // monorepo). Fall back to monorepo source paths for local development.
35
+ // dist/index.js -> packages/mcp-server
36
+ const PKG_ROOT = path.resolve(__dirname, "..");
37
+ // dist/index.js -> packages/mcp-server -> packages -> repo root
38
+ const REPO_ROOT = path.resolve(__dirname, "..", "..", "..");
39
+ function firstExisting(...candidates) {
40
+ for (const c of candidates) {
41
+ if (fs.existsSync(c))
42
+ return c;
43
+ }
44
+ return null;
45
+ }
46
+ // The aggregated registry array (every item with embedded source) is the single
47
+ // source of truth for list/get/search. component-catalog.json adds human
48
+ // descriptions + category tags.
49
+ const REGISTRY_JSON = firstExisting(path.join(PKG_ROOT, "registry", "registry.json"), path.join(REPO_ROOT, "packages", "registry", "generated", "registry.json"));
50
+ const CATALOG_JSON = firstExisting(path.join(PKG_ROOT, "registry", "component-catalog.json"), path.join(REPO_ROOT, "apps", "docs", "src", "data", "component-catalog.json"));
51
+ // ---------------------------------------------------------------------------
52
+ // Lazy-loaded data (only read once per process).
53
+ // ---------------------------------------------------------------------------
54
+ let _items = null;
55
+ let _catalog = null;
56
+ function readJson(filePath) {
57
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
58
+ }
59
+ function loadItems() {
60
+ if (!_items) {
61
+ if (!REGISTRY_JSON) {
62
+ throw new Error("Lerpa UI registry data not found. From a monorepo checkout run " +
63
+ "`pnpm --filter @lerpa/registry run build`; a published package " +
64
+ "ships registry/registry.json (see scripts/copy-registry.js).");
65
+ }
66
+ _items = readJson(REGISTRY_JSON);
67
+ }
68
+ return _items;
69
+ }
70
+ function loadCatalog() {
71
+ if (!_catalog) {
72
+ try {
73
+ if (!CATALOG_JSON) {
74
+ _catalog = [];
75
+ return _catalog;
76
+ }
77
+ // component-catalog.json is { categories: [...], components: [...] };
78
+ // tolerate a bare array too.
79
+ const raw = readJson(CATALOG_JSON);
80
+ const arr = Array.isArray(raw)
81
+ ? raw
82
+ : (raw.components ?? []);
83
+ _catalog = arr;
84
+ }
85
+ catch {
86
+ _catalog = [];
87
+ }
88
+ }
89
+ return _catalog;
90
+ }
91
+ function findItem(id) {
92
+ const item = loadItems().find((it) => it.name === id);
93
+ if (!item) {
94
+ throw new Error(`Component not found: ${id}`);
95
+ }
96
+ return item;
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Tool implementations.
100
+ // ---------------------------------------------------------------------------
101
+ function tool_list_components(category) {
102
+ const catalog = loadCatalog();
103
+ // Build a quick lookup from id -> catalog entry for category info.
104
+ const catById = new Map(catalog.map((c) => [c.id ?? c.name, c]));
105
+ let items = loadItems();
106
+ if (category) {
107
+ const cat = category.toLowerCase();
108
+ items = items.filter((it) => {
109
+ const entry = catById.get(it.name);
110
+ if (entry?.category?.toLowerCase() === cat)
111
+ return true;
112
+ // Fallback: name-prefix heuristic (e.g. "ai-chat-stream" -> "ai")
113
+ return it.name.toLowerCase().startsWith(`${cat}-`);
114
+ });
115
+ }
116
+ return {
117
+ total: items.length,
118
+ category: category ?? "all",
119
+ items: items.map((it) => ({
120
+ id: it.name,
121
+ type: it.type,
122
+ install: `pnpm dlx lerpa-cli add ${it.name}`,
123
+ registryUrl: `https://lerpaui.com/r/${it.name}`,
124
+ })),
125
+ };
126
+ }
127
+ function tool_get_component(id) {
128
+ const item = findItem(id);
129
+ return {
130
+ id: item.name,
131
+ type: item.type,
132
+ dependencies: item.dependencies ?? [],
133
+ registryDependencies: item.registryDependencies ?? [],
134
+ install: `pnpm dlx lerpa-cli add ${item.name}`,
135
+ files: item.files.map((f) => ({
136
+ path: f.path,
137
+ type: f.type,
138
+ source: f.content,
139
+ })),
140
+ };
141
+ }
142
+ function tool_search_components(query) {
143
+ const q = query.trim().toLowerCase();
144
+ if (!q)
145
+ return { total: 0, results: [] };
146
+ const catalog = loadCatalog();
147
+ const catById = new Map(catalog.map((c) => [c.id ?? c.name, c]));
148
+ const scored = [];
149
+ for (const it of loadItems()) {
150
+ const name = it.name.toLowerCase();
151
+ const entry = catById.get(it.name);
152
+ const desc = entry?.description?.toLowerCase() ?? "";
153
+ let score = 0;
154
+ if (name === q)
155
+ score += 100;
156
+ if (name.includes(q))
157
+ score += 50;
158
+ if (name.startsWith(q))
159
+ score += 25;
160
+ for (const token of q.split(/[\s-]+/).filter(Boolean)) {
161
+ if (name.includes(token))
162
+ score += 5;
163
+ if (desc.includes(token))
164
+ score += 3;
165
+ }
166
+ if (score > 0) {
167
+ scored.push({ id: it.name, type: it.type, score, description: entry?.description });
168
+ }
169
+ }
170
+ scored.sort((a, b) => b.score - a.score);
171
+ return {
172
+ total: scored.length,
173
+ query,
174
+ results: scored.slice(0, 50).map(({ score: _score, ...rest }) => rest),
175
+ };
176
+ }
177
+ // ---------------------------------------------------------------------------
178
+ // MCP server wiring.
179
+ // ---------------------------------------------------------------------------
180
+ const TOOLS = [
181
+ {
182
+ name: "list_components",
183
+ description: "List Lerpa UI components from the registry, optionally filtered by category " +
184
+ "(ai, buttons, cards, forms, creative, feedback, navigation, tables, calendars, " +
185
+ "ecommerce, dashboard, auth, account, docs, blog).",
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ category: {
190
+ type: "string",
191
+ description: "Optional category slug to filter results.",
192
+ },
193
+ },
194
+ },
195
+ },
196
+ {
197
+ name: "get_component",
198
+ description: "Get the full registry item JSON for a single component, including embedded " +
199
+ "source code from `packages/registry/items/<id>.json`.",
200
+ inputSchema: {
201
+ type: "object",
202
+ required: ["id"],
203
+ properties: {
204
+ id: { type: "string", description: "Component id (registry name)." },
205
+ },
206
+ },
207
+ },
208
+ {
209
+ name: "search_components",
210
+ description: "Fuzzy search across all Lerpa UI registry items by name and description. Returns up to 50 matches.",
211
+ inputSchema: {
212
+ type: "object",
213
+ required: ["query"],
214
+ properties: {
215
+ query: { type: "string", description: "Free-text query." },
216
+ },
217
+ },
218
+ },
219
+ ];
220
+ async function main() {
221
+ const server = new Server({ name: "@lerpa/mcp-server", version: "0.1.0" }, { capabilities: { tools: {} } });
222
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
223
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
224
+ const { name, arguments: args = {} } = request.params;
225
+ try {
226
+ let result;
227
+ switch (name) {
228
+ case "list_components":
229
+ result = tool_list_components(args.category);
230
+ break;
231
+ case "get_component":
232
+ result = tool_get_component(args.id);
233
+ break;
234
+ case "search_components":
235
+ result = tool_search_components(args.query);
236
+ break;
237
+ default:
238
+ throw new Error(`Unknown tool: ${name}`);
239
+ }
240
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
241
+ }
242
+ catch (err) {
243
+ const message = err instanceof Error ? err.message : String(err);
244
+ return {
245
+ isError: true,
246
+ content: [{ type: "text", text: `Error in ${name}: ${message}` }],
247
+ };
248
+ }
249
+ });
250
+ const transport = new StdioServerTransport();
251
+ await server.connect(transport);
252
+ // eslint-disable-next-line no-console
253
+ console.error("[lerpa-mcp] stdio server ready");
254
+ }
255
+ main().catch((err) => {
256
+ // eslint-disable-next-line no-console
257
+ console.error("[lerpa-mcp] fatal:", err);
258
+ process.exit(1);
259
+ });
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@lerpa/mcp-server",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Model Context Protocol server exposing the Lerpa UI registry to AI agents (Claude Code, Cursor, v0, Continue).",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/cuibit-labs/lerpaui.git",
9
+ "directory": "packages/mcp-server"
10
+ },
11
+ "author": "Lerpa UI",
12
+ "homepage": "https://github.com/cuibit-labs/lerpaui#readme",
13
+ "bugs": "https://github.com/cuibit-labs/lerpaui/issues",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "bin": {
21
+ "lerpa-mcp": "./dist/index.js"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "registry",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc && node ./scripts/copy-registry.cjs",
36
+ "dev": "tsc -w",
37
+ "lint": "eslint src --ext .ts",
38
+ "typecheck": "tsc --noEmit",
39
+ "start": "node ./dist/index.js",
40
+ "prepublishOnly": "pnpm --filter @lerpa/registry run build && pnpm run build"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.11.24",
44
+ "typescript": "^5.4.5"
45
+ },
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "keywords": [
50
+ "mcp",
51
+ "model-context-protocol",
52
+ "lerpa",
53
+ "shadcn",
54
+ "registry",
55
+ "ai",
56
+ "claude",
57
+ "cursor",
58
+ "v0"
59
+ ],
60
+ "dependencies": {
61
+ "@modelcontextprotocol/sdk": "^1.29.0"
62
+ }
63
+ }