aura-components-mcp 1.6.0 → 1.6.2
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/dist/server.js +327 -163
- package/package.json +1 -2
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// aura-components-mcp v1.6.
|
|
2
|
+
// aura-components-mcp v1.6.1 — bundled (zero deps). Built by swastiksingh-dev.
|
|
3
3
|
|
|
4
4
|
// ---- config.mjs ----
|
|
5
5
|
// Module: config — one small interface (loadConfig) over all env parsing.
|
|
@@ -121,9 +121,38 @@ const esc = (s) => String(s).replaceAll('"', '""');
|
|
|
121
121
|
const ilike = (v) => "*" + String(v).replaceAll("*", "").replaceAll(",", " ").trim() + "*";
|
|
122
122
|
|
|
123
123
|
// Split a goal sentence into significant tokens for OR fallback (stop-word filtered).
|
|
124
|
-
|
|
124
|
+
// Theme words (dark/light) are NOT search tokens — they become rank/filter signals
|
|
125
|
+
// (see detectThemeHint + scoreByTheme), so "dark cinematic" can't match light rows.
|
|
126
|
+
const STOP = new Set("a,an,the,for,with,and,or,of,to,in,on,my,new,free,also,that,this,from,into,plus,vs,top,best,up".split(","));
|
|
127
|
+
const THEME_WORDS = new Set(["dark", "light", "midnight", "noir", "black", "white", "bright", "airy", "minimal"]);
|
|
125
128
|
function queryTokens(q, max = 4) {
|
|
126
|
-
return String(q ?? "").toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).map((t) => t.trim()).filter((t) => t.length > 2 && !STOP.has(t)).slice(0, max);
|
|
129
|
+
return String(q ?? "").toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).map((t) => t.trim()).filter((t) => t.length > 2 && !STOP.has(t) && !THEME_WORDS.has(t)).slice(0, max);
|
|
130
|
+
}
|
|
131
|
+
// Theme hint from the raw query: explicit signal, separate from match tokens.
|
|
132
|
+
function detectThemeHint(q) {
|
|
133
|
+
const t = String(q ?? "").toLowerCase();
|
|
134
|
+
const dark = /\bdark\b|\bmidnight\b|\bnoir\b|\bcinematic\b/.test(t);
|
|
135
|
+
const light = /\blight\b|\bbright\b|\bairy\b/.test(t);
|
|
136
|
+
if (dark && !light) return "dark";
|
|
137
|
+
if (light && !dark) return "light";
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// Rank rows: +2 per matched token in title, +1 in description/tags, theme match first.
|
|
141
|
+
// Returns { rows, scored } where scored explains the top pick for fallback_score.
|
|
142
|
+
function scoreRows(kind, rows, toks, themeHint) {
|
|
143
|
+
const scored = rows.map((r) => {
|
|
144
|
+
const title = String(r.title || "").toLowerCase();
|
|
145
|
+
const desc = String(r.description || "").toLowerCase();
|
|
146
|
+
const tags = ((r.tags || []).map((x) => String(x).toLowerCase()).join(" "));
|
|
147
|
+
let s = 0;
|
|
148
|
+
for (const t of toks) { if (title.includes(t)) s += 2; else if (desc.includes(t) || tags.includes(t)) s += 1; }
|
|
149
|
+
const th = r.facets && r.facets.theme;
|
|
150
|
+
const themeOk = !themeHint || th === themeHint || th === "mixed" || th === "unknown";
|
|
151
|
+
if (themeHint && th === themeHint) s += 3;
|
|
152
|
+
return { r, s, themeOk };
|
|
153
|
+
});
|
|
154
|
+
scored.sort((a, b) => ((b.themeOk ? 1 : 0) - (a.themeOk ? 1 : 0)) || (b.s - a.s) || ((b.r.views || 0) - (a.r.views || 0)));
|
|
155
|
+
return scored;
|
|
127
156
|
}
|
|
128
157
|
|
|
129
158
|
function windowAgoIso(days) {
|
|
@@ -132,7 +161,7 @@ function windowAgoIso(days) {
|
|
|
132
161
|
return d.toISOString();
|
|
133
162
|
}
|
|
134
163
|
function weekAgoIso() { return windowAgoIso(7); }
|
|
135
|
-
function trendingIso() { return windowAgoIso(
|
|
164
|
+
function trendingIso(days = 90) { return windowAgoIso(days); } // 7d seed is empty; callers pass window_days (default 30 for components now)
|
|
136
165
|
|
|
137
166
|
function buildSearchParams(kind, q = {}) {
|
|
138
167
|
const p = new URLSearchParams();
|
|
@@ -160,7 +189,7 @@ function buildSearchParams(kind, q = {}) {
|
|
|
160
189
|
else if (kind === "assets") ors.push("title.ilike." + v + ",description.ilike." + v);
|
|
161
190
|
else ors.push("title.ilike." + v + ",description.ilike." + v);
|
|
162
191
|
}
|
|
163
|
-
if (q.sort === "trending") filters.push("created_at=gte." + trendingIso());
|
|
192
|
+
if (q.sort === "trending") filters.push("created_at=gte." + trendingIso(q.window_days ?? 90));
|
|
164
193
|
return { params: p, filters, ors };
|
|
165
194
|
}
|
|
166
195
|
|
|
@@ -171,7 +200,15 @@ function buildUrl(base, table, params, filters, ors) {
|
|
|
171
200
|
return base + "/rest/v1/" + table + "?" + qs;
|
|
172
201
|
}
|
|
173
202
|
|
|
203
|
+
// Truncation with cursors: agents see full_length + chunk info instead of a silent
|
|
204
|
+
// marker, and can fetch the rest via getItem with { chunk } (see getItem below).
|
|
174
205
|
const trunc = (s, n) => (s && s.length > n ? s.slice(0, n) + "…[truncated]" : s);
|
|
206
|
+
function chunkText(s, n, chunk = 0) {
|
|
207
|
+
const full = String(s || "");
|
|
208
|
+
const total = Math.max(1, Math.ceil(full.length / n));
|
|
209
|
+
const i = Math.max(0, Math.min(chunk, total - 1));
|
|
210
|
+
return { text: full.slice(i * n, (i + 1) * n) + (i < total - 1 ? "…[truncated]" : ""), chunk: i, chunks: total, full_length: full.length, truncated: total > 1 };
|
|
211
|
+
}
|
|
175
212
|
|
|
176
213
|
function pageUrl(kind, row) {
|
|
177
214
|
if (kind === "components") return SITE + "/component/" + row.slug;
|
|
@@ -193,12 +230,31 @@ function shapeRow(kind, row, cfg, withFacets) {
|
|
|
193
230
|
if (kind === "components" && typeof base.code === "string") {
|
|
194
231
|
base.code_chars = base.code.length;
|
|
195
232
|
if (withFacets) { try { base.facets = withFacets(kind, { ...base, code: base.code.slice(0, 600) }, { fullLength: base.code.length }); } catch { /* never break rows */ } }
|
|
196
|
-
|
|
233
|
+
// Gap 3: code_excerpt (first 1200 chars, readable) + full_fetch flag. Agents skim
|
|
234
|
+
// the excerpt, then get chunk N — no more blind 18k-70k dumps or silent cuts.
|
|
235
|
+
base.code_excerpt = String(base.code).slice(0, 1200);
|
|
236
|
+
base.full_fetch = { tool: "aura_get_component", id: base.id ?? base.slug, chunks: Math.max(1, Math.ceil(base.code.length / cfg.codeChars)) };
|
|
237
|
+
const ch = chunkText(base.code, cfg.codeChars, 0);
|
|
238
|
+
base.code = ch.text;
|
|
239
|
+
base.code_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: 0, truncated: ch.truncated };
|
|
240
|
+
if (base.facets) base.facets.deps_summary = base.facets.needsTailwind || base.facets.needsIcons ? [base.facets.needsTailwind && "tailwindcss", base.facets.needsIcons && "iconify-icon"].filter(Boolean) : [];
|
|
241
|
+
}
|
|
242
|
+
if (kind === "skills" && typeof base.content === "string") {
|
|
243
|
+
const ch = chunkText(base.content, cfg.contentChars, 0);
|
|
244
|
+
base.content = ch.text;
|
|
245
|
+
base.content_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: 0, truncated: ch.truncated };
|
|
197
246
|
}
|
|
198
|
-
if (kind === "skills" && typeof base.content === "string") base.content = trunc(base.content, cfg.contentChars);
|
|
199
247
|
if (kind === "design_systems") {
|
|
200
|
-
if (typeof base.content === "string")
|
|
201
|
-
|
|
248
|
+
if (typeof base.content === "string") {
|
|
249
|
+
const ch = chunkText(base.content, cfg.contentChars, 0);
|
|
250
|
+
base.content = ch.text;
|
|
251
|
+
base.content_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: 0, truncated: ch.truncated };
|
|
252
|
+
}
|
|
253
|
+
if (typeof base.preview_html === "string") {
|
|
254
|
+
const ch = chunkText(base.preview_html, cfg.codeChars, 0);
|
|
255
|
+
base.preview_html = ch.text;
|
|
256
|
+
base.preview_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: 0, truncated: ch.truncated };
|
|
257
|
+
}
|
|
202
258
|
}
|
|
203
259
|
base.page_url = pageUrl(kind, row);
|
|
204
260
|
if (kind === "assets") base.asset_key = assetKey(row);
|
|
@@ -256,7 +312,11 @@ function createCatalog({ fetcher, config, now = () => Date.now(), facetsFn = nul
|
|
|
256
312
|
const offset = Math.max(0, q.offset ?? 0);
|
|
257
313
|
const sort = CATALOG_SORTS[kind][q.sort] ? q.sort : "popular";
|
|
258
314
|
const freeOnly = q.freeOnly ?? config.freeOnlyDefault;
|
|
259
|
-
|
|
315
|
+
// Gap 1: negative_theme (exclude a theme) + min_views (quality floor). Both ride
|
|
316
|
+
// the cache key so filtered/unfiltered variants never collide.
|
|
317
|
+
const negTheme = q.negative_theme ? String(q.negative_theme).toLowerCase() : "";
|
|
318
|
+
const minViews = Math.max(0, q.min_views ?? 0);
|
|
319
|
+
const key = ["search", kind, q.query ?? "", q.tag ?? "", q.mediaType ?? "", sort, limit, offset, freeOnly, negTheme, minViews].join("|");
|
|
260
320
|
const hit = cacheGet(key);
|
|
261
321
|
if (hit) return { ...hit, cached: true };
|
|
262
322
|
if (inflight.has(key)) return inflight.get(key);
|
|
@@ -270,8 +330,10 @@ function createCatalog({ fetcher, config, now = () => Date.now(), facetsFn = nul
|
|
|
270
330
|
let fallback = null;
|
|
271
331
|
// AND-phrase queries like "dark cinematic portfolio" match nothing literally:
|
|
272
332
|
// retry as OR over significant tokens so every surface degrades instead of emptying.
|
|
273
|
-
//
|
|
333
|
+
// Theme words (dark/light/cinematic) are rank signals, not match tokens.
|
|
274
334
|
const toks = (q.query && q.query.trim() && !q.tag) ? queryTokens(q.query) : [];
|
|
335
|
+
const themeHint = (q.query && q.query.trim()) ? detectThemeHint(q.query) : null;
|
|
336
|
+
const effTheme = q.theme || themeHint;
|
|
275
337
|
const suggest = (t) => {
|
|
276
338
|
const s = [];
|
|
277
339
|
if (t.length > 1) s.push(t.slice(0, Math.min(3, t.length)).join(" "));
|
|
@@ -322,16 +384,59 @@ function createCatalog({ fetcher, config, now = () => Date.now(), facetsFn = nul
|
|
|
322
384
|
return (u && byUrl.get(u) === r) ? { ...r, canonical: true } : r;
|
|
323
385
|
});
|
|
324
386
|
}
|
|
387
|
+
// Gap 4: inline snippets — one extra batched fetch fills preview/tokens glimpses
|
|
388
|
+
// into search rows (top N only) so agents decide without a second call per row.
|
|
389
|
+
// Bounded: top 5 rows, client-side sliced to 800 chars. No extra call when empty.
|
|
390
|
+
if ((kind === "design_systems" || kind === "skills") && rows.length) {
|
|
391
|
+
const topIds = rows.slice(0, 5).map((r) => r.id);
|
|
392
|
+
try {
|
|
393
|
+
const idList = topIds.map((id) => encodeURIComponent(String(id))).join(",");
|
|
394
|
+
const sel = kind === "design_systems" ? "id,content,preview_html" : "id,content,source_url";
|
|
395
|
+
const { rows: full } = await fetcher.getJson(base + "/rest/v1/" + kind + "?select=" + encodeURIComponent(sel) + "&id=in.(" + idList + ")", headers);
|
|
396
|
+
const byId = new Map(full.map((r) => [String(r.id), r]));
|
|
397
|
+
rows = rows.map((r) => {
|
|
398
|
+
const f = byId.get(String(r.id));
|
|
399
|
+
if (!f) return r;
|
|
400
|
+
if (kind === "design_systems") return { ...r, preview_snippet: String(f.preview_html || "").slice(0, 800), tokens_snippet: String(f.content || "").slice(0, 800) };
|
|
401
|
+
return { ...r, content_snippet: String(f.content || "").slice(0, 800) };
|
|
402
|
+
});
|
|
403
|
+
} catch { /* snippets are best-effort; rows still return */ }
|
|
404
|
+
}
|
|
325
405
|
const enriched = await enrichAuthors(rows);
|
|
326
406
|
const out = { kind, total, limit, offset, items: enriched.map((r) => shapeRow(kind, r, config, facetsFn)), cached: false };
|
|
327
407
|
if (deduped) out.deduped = deduped;
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
408
|
+
// Gap 1: min_views quality floor (views-gated surfaces only) + negative_theme
|
|
409
|
+
// hard exclusion. Both apply before theme re-rank so counts stay honest.
|
|
410
|
+
if (minViews > 0) out.items = out.items.filter((it) => (it.views || 0) >= minViews);
|
|
411
|
+
if (negTheme && kind === "components") {
|
|
412
|
+
const before = out.items.length;
|
|
413
|
+
out.items = out.items.filter((it) => !it.facets || it.facets.theme !== negTheme);
|
|
414
|
+
if (out.items.length < before) out.negative_theme_filtered_out = before - out.items.length;
|
|
415
|
+
}
|
|
416
|
+
// Theme-aware re-rank (components): theme hint from query (dark/light/cinematic)
|
|
417
|
+
// or explicit q.theme reorders so matches come first — never silently drops rows,
|
|
418
|
+
// but reports how many were filtered out of the top for transparency.
|
|
419
|
+
if (kind === "components" && effTheme) {
|
|
420
|
+
const scored = scoreRows(kind, out.items, toks, effTheme);
|
|
421
|
+
const topTheme = scored.filter((s) => s.themeOk).map((s) => s.r);
|
|
422
|
+
const dropped = out.items.length - topTheme.length;
|
|
423
|
+
if (topTheme.length) {
|
|
424
|
+
out.items = topTheme;
|
|
425
|
+
out.theme_hint = effTheme;
|
|
426
|
+
if (dropped) out.theme_filtered_out = dropped;
|
|
427
|
+
} else {
|
|
428
|
+
out.theme_hint = effTheme;
|
|
429
|
+
out.theme_warning = "no " + effTheme + "-theme rows in top results; showing unfiltered, check facets.theme per row";
|
|
430
|
+
}
|
|
332
431
|
}
|
|
333
432
|
// ISS15: transparent fallback — agents see what happened, not a magic string.
|
|
334
|
-
if (fallback) {
|
|
433
|
+
if (fallback) {
|
|
434
|
+
out.fallback = fallback; out.isFallback = true;
|
|
435
|
+
const scoredTop = out.items[0];
|
|
436
|
+
const topTitle = scoredTop ? String(scoredTop.title || "").toLowerCase() : "";
|
|
437
|
+
const hits = toks.filter((t) => topTitle.includes(t)).length;
|
|
438
|
+
out.fallback_score = "token-overlap:" + hits + "/" + toks.length + "-terms" + (effTheme ? ";theme:" + effTheme : "");
|
|
439
|
+
}
|
|
335
440
|
else if ((!rows || !rows.length) && toks.length) { out.isFallback = false; out.suggested_queries = suggest(toks).filter((s) => !/^[a-z0-9]{8,}$/i.test(s.replace(/\s/g, ""))); if (!out.suggested_queries.length) out.suggested_queries = ["hero", "pricing", "landing page"]; }
|
|
336
441
|
cacheSet(key, out);
|
|
337
442
|
return out;
|
|
@@ -340,8 +445,9 @@ function createCatalog({ fetcher, config, now = () => Date.now(), facetsFn = nul
|
|
|
340
445
|
try { return await p; } finally { inflight.delete(key); }
|
|
341
446
|
}
|
|
342
447
|
|
|
343
|
-
async function getItem(kind, idOrSlug) {
|
|
344
|
-
const
|
|
448
|
+
async function getItem(kind, idOrSlug, opts = {}) {
|
|
449
|
+
const chunk = Math.max(0, opts.chunk ?? 0);
|
|
450
|
+
const key = ["get", kind, String(idOrSlug), "c" + chunk].join("|");
|
|
345
451
|
const hit = cacheGet(key);
|
|
346
452
|
if (hit) return { ...hit, cached: true };
|
|
347
453
|
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(String(idOrSlug));
|
|
@@ -357,7 +463,35 @@ function createCatalog({ fetcher, config, now = () => Date.now(), facetsFn = nul
|
|
|
357
463
|
const { rows } = await fetcher.getJson(url, headers);
|
|
358
464
|
if (!rows.length) { const e = new Error("not found: " + kind + " " + idOrSlug); e.code = "NOT_FOUND"; throw e; }
|
|
359
465
|
const enriched = await enrichAuthors(rows);
|
|
360
|
-
const
|
|
466
|
+
const full = enriched[0];
|
|
467
|
+
// Chunked fetch: re-slice full blobs per requested chunk so agents can page
|
|
468
|
+
// through 18k-70k payloads instead of hitting one silent truncation wall.
|
|
469
|
+
const item = shapeRow(kind, full, config, facetsFn);
|
|
470
|
+
if (chunk > 0) {
|
|
471
|
+
if (kind === "components" && typeof full.code === "string") {
|
|
472
|
+
const ch = chunkText(full.code, config.codeChars, chunk);
|
|
473
|
+
item.code = ch.text;
|
|
474
|
+
item.code_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: ch.chunk, truncated: ch.truncated };
|
|
475
|
+
}
|
|
476
|
+
if ((kind === "skills") && typeof full.content === "string") {
|
|
477
|
+
const ch = chunkText(full.content, config.contentChars, chunk);
|
|
478
|
+
item.content = ch.text;
|
|
479
|
+
item.content_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: ch.chunk, truncated: ch.truncated };
|
|
480
|
+
}
|
|
481
|
+
if (kind === "design_systems") {
|
|
482
|
+
if (typeof full.content === "string") {
|
|
483
|
+
const ch = chunkText(full.content, config.contentChars, chunk);
|
|
484
|
+
item.content = ch.text;
|
|
485
|
+
item.content_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: ch.chunk, truncated: ch.truncated };
|
|
486
|
+
}
|
|
487
|
+
if (typeof full.preview_html === "string") {
|
|
488
|
+
const ch = chunkText(full.preview_html, config.codeChars, chunk);
|
|
489
|
+
item.preview_html = ch.text;
|
|
490
|
+
item.preview_info = { full_length: ch.full_length, chunks: ch.chunks, chunk: ch.chunk, truncated: ch.truncated };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const out = { kind, item, cached: false };
|
|
361
495
|
cacheSet(key, out);
|
|
362
496
|
return out;
|
|
363
497
|
}
|
|
@@ -399,6 +533,19 @@ function createCatalog({ fetcher, config, now = () => Date.now(), facetsFn = nul
|
|
|
399
533
|
return out;
|
|
400
534
|
}
|
|
401
535
|
|
|
536
|
+
// Gap 2: editorial picks — human-grade component leads for when views-signal is
|
|
537
|
+
// noise (free trending total=2). Top free by forks (remix = real reuse), then views.
|
|
538
|
+
async function editorialPicks(limit = 5) {
|
|
539
|
+
const lim = Math.max(1, Math.min(limit || 5, 8));
|
|
540
|
+
const sel = encodeURIComponent(LIST_COLS.components);
|
|
541
|
+
const url = base + "/rest/v1/components?select=" + sel + "&private=eq.false&premium=eq.false&order=forks.desc,views.desc&limit=" + lim;
|
|
542
|
+
try {
|
|
543
|
+
const { rows } = await fetcher.getJson(url, headers);
|
|
544
|
+
const enriched = await enrichAuthors(rows);
|
|
545
|
+
return enriched.map((r) => shapeRow("components", r, config, facetsFn));
|
|
546
|
+
} catch { return []; }
|
|
547
|
+
}
|
|
548
|
+
|
|
402
549
|
// Bulk fetch: N details in one parallel round (backs aura_bundle + aura_scaffold_page).
|
|
403
550
|
// Per-id errors are captured, never thrown: { ok:true, item } or { ok:false, id, error }.
|
|
404
551
|
async function bundleItems(kind, ids, max = 5) {
|
|
@@ -431,11 +578,11 @@ function createCatalog({ fetcher, config, now = () => Date.now(), facetsFn = nul
|
|
|
431
578
|
} catch { return { item: shapeRow(kind, item, config), related: [] }; }
|
|
432
579
|
}
|
|
433
580
|
|
|
434
|
-
return { searchCatalog, getItem, getStatus, categoryCounts, bundleItems, relatedItems };
|
|
581
|
+
return { searchCatalog, getItem, getStatus, categoryCounts, bundleItems, relatedItems, editorialPicks };
|
|
435
582
|
}
|
|
436
583
|
|
|
437
584
|
// ---- protocol.mjs ----
|
|
438
|
-
const SERVER_INFO = { name: "aura-components-mcp", version: "1.6.
|
|
585
|
+
const SERVER_INFO = { name: "aura-components-mcp", version: "1.6.1" };
|
|
439
586
|
// Module: protocol — stdio JSON-RPC framing + MCP handshake + error codes.
|
|
440
587
|
// One interface: createSession(send) -> { dispatch(msg) }. No business logic.
|
|
441
588
|
|
|
@@ -576,6 +723,10 @@ function facets(kind, row, opts) {
|
|
|
576
723
|
// catalogue content is the exclusive property of DESIGNCODE IO PTE. LTD.;
|
|
577
724
|
// no per-asset license column exists, so commercial reuse needs Aura's permission.
|
|
578
725
|
f.license = 'all-rights-reserved (Aura Terms §4: DESIGNCODE IO PTE. LTD.) — personal/preview use via page_url; commercial reuse needs Aura permission (support@designcode.io)';
|
|
726
|
+
// Gap 5: machine-readable license gate. commercial_ok:false always (no per-asset
|
|
727
|
+
// column exists to prove otherwise) — agents filter without parsing prose.
|
|
728
|
+
f.commercial_ok = false;
|
|
729
|
+
f.terms_url = 'https://www.aura.build/terms';
|
|
579
730
|
f.download = row.image_original || row.image_1600w || row.image_800w || row.video_url || null;
|
|
580
731
|
f.preview = row.image_800w || row.video_poster_url || null;
|
|
581
732
|
}
|
|
@@ -713,148 +864,161 @@ function tokenHints(content, previewHtml) {
|
|
|
713
864
|
};
|
|
714
865
|
|
|
715
866
|
// ---- tools.mjs ----
|
|
716
|
-
// Module: tools
|
|
717
|
-
// calls one catalog/guide method, wraps textResult. No retries/caching here.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
const
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
function
|
|
725
|
-
function
|
|
726
|
-
function
|
|
727
|
-
function
|
|
728
|
-
function
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
{ name: '
|
|
735
|
-
{ name: '
|
|
736
|
-
{ name: '
|
|
737
|
-
{ name: '
|
|
738
|
-
{ name: '
|
|
739
|
-
{ name: '
|
|
740
|
-
{ name: '
|
|
741
|
-
{ name: '
|
|
742
|
-
{ name: '
|
|
743
|
-
{ name: '
|
|
744
|
-
{ name: '
|
|
745
|
-
{ name: '
|
|
746
|
-
{ name: '
|
|
747
|
-
{ name: '
|
|
748
|
-
{ name: '
|
|
749
|
-
{ name: '
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
catalog.searchCatalog('
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
components:
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
if (!ids || !ids.length) toolFn_bad('provide ids (array of 1-8)');
|
|
839
|
-
return textResult({ kind, results: await catalog.bundleItems(
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
}
|
|
867
|
+
// Module: tools — thin adapters. Each tool validates args at the seam,
|
|
868
|
+
// calls one catalog/guide method, wraps textResult. No retries/caching here.
|
|
869
|
+
const TOOL_KINDS = ['components', 'skills', 'assets', 'design_systems'];
|
|
870
|
+
const TOOL_SORTS = ['popular', 'recent', 'trending', 'updated'];
|
|
871
|
+
const TOOL_CATS = ['hero','section','button','card','background','header','logo','feature','pricing','testimonial','footer','form','heading'];
|
|
872
|
+
function toolFn_bad(msg) { const e = new Error(msg); e.code = 'BAD_ARGS'; throw e; }
|
|
873
|
+
function toolFn_str(v, name) { if (v !== undefined && typeof v !== 'string') toolFn_bad(name + ' must be a string'); return v; }
|
|
874
|
+
function toolFn_num(v, name) { if (v !== undefined && typeof v !== 'number') toolFn_bad(name + ' must be a number'); return v; }
|
|
875
|
+
function toolFn_bool(v, name) { if (v !== undefined && typeof v !== 'boolean') toolFn_bad(name + ' must be a boolean'); return v; }
|
|
876
|
+
function toolFn_sort(v) { if (v !== undefined && !TOOL_SORTS.includes(v)) toolFn_bad('sort must be one of ' + TOOL_SORTS.join('|')); return v; }
|
|
877
|
+
function theme(v) { if (v !== undefined && v !== 'dark' && v !== 'light') toolFn_bad('theme must be dark|light'); return v; }
|
|
878
|
+
function idList(v, name) { if (v === undefined) return v; if (!Array.isArray(v) || !v.length || v.length > 8 || v.some((x) => typeof x !== 'string' && typeof x !== 'number')) toolFn_bad(name + ' must be an array of 1-8 ids'); return v; }
|
|
879
|
+
function toolFn_cat(v) { if (v !== undefined && !TOOL_CATS.includes(v)) toolFn_bad('category must be one of ' + TOOL_CATS.join('|')); return v; }
|
|
880
|
+
const TOOL_DEFS = [
|
|
881
|
+
{ name: 'aura_status', description: 'Catalogue health plus free counts (components, skills, assets, design systems). Free only, no login. Start here.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
|
|
882
|
+
{ name: 'aura_search_components', description: 'Search free Aura UI components (2,495). Text over title and description, optional category tag, theme dark|light filter, negative_theme exclusion, min_views floor, sorts. Items carry facets (theme/weight/needs).', inputSchema: { type: 'object', properties: { query: { type: 'string' }, category: { type: 'string', enum: TOOL_CATS }, theme: { type: 'string', enum: ['dark', 'light'] }, negative_theme: { type: 'string', enum: ['dark', 'light'] }, min_views: { type: 'number' }, sort: { type: 'string', enum: TOOL_SORTS }, limit: { type: 'number' }, offset: { type: 'number' } }, additionalProperties: false } },
|
|
883
|
+
{ name: 'aura_get_component', description: 'Full free component detail with HTML/Tailwind source, preview image, page URL. Numeric id or slug. Chunk param pages large code (see code_info).', inputSchema: { type: 'object', properties: { id: {}, slug: { type: 'string' }, chunk: { type: 'number' } }, additionalProperties: false } },
|
|
884
|
+
{ name: 'aura_search_skills', description: 'Search free Aura agent skills (187). Metadata only; use aura_get_skill for the full SKILL.md content.', inputSchema: { type: 'object', properties: { query: { type: 'string' }, sort: { type: 'string', enum: TOOL_SORTS }, limit: { type: 'number' }, offset: { type: 'number' } }, additionalProperties: false } },
|
|
885
|
+
{ name: 'aura_get_skill', description: 'Full free agent-skill content (SKILL.md body) plus source_url and page URL. Skill id. Chunk param pages large bodies (see content_info).', inputSchema: { type: 'object', properties: { id: { type: 'string' }, chunk: { type: 'number' } }, required: ['id'], additionalProperties: false } },
|
|
886
|
+
{ name: 'aura_search_assets', description: 'Search free Aura assets (images and video). Keywords, media_type image or video.', inputSchema: { type: 'object', properties: { query: { type: 'string' }, mediaType: { type: 'string', enum: ['image', 'video'] }, commercial_ok: { type: 'boolean' }, sort: { type: 'string', enum: TOOL_SORTS }, limit: { type: 'number' }, offset: { type: 'number' } }, additionalProperties: false } },
|
|
887
|
+
{ name: 'aura_search_design_systems', description: 'Search free Aura DESIGN.md systems (725). Metadata only; use aura_get_design_system for content.', inputSchema: { type: 'object', properties: { query: { type: 'string' }, sort: { type: 'string', enum: TOOL_SORTS }, limit: { type: 'number' }, offset: { type: 'number' } }, additionalProperties: false } },
|
|
888
|
+
{ name: 'aura_get_design_system', description: 'Full free DESIGN.md content plus preview_html, tokens, and page URL. System id or slug. Chunk param pages large preview_html (see preview_info).', inputSchema: { type: 'object', properties: { id: { type: 'string' }, slug: { type: 'string' }, chunk: { type: 'number' } }, additionalProperties: false } },
|
|
889
|
+
{ name: 'aura_search_all', description: 'One call across components, skills, assets, and design systems in parallel. Free only.', inputSchema: { type: 'object', properties: { query: { type: 'string' }, limit: { type: 'number' } }, additionalProperties: false } },
|
|
890
|
+
{ name: 'aura_recommend', description: 'Starter kit for a goal: top free components, skills, design systems, and assets with page URLs and rationale.', inputSchema: { type: 'object', properties: { goal: { type: 'string' } }, required: ['goal'], additionalProperties: false } },
|
|
891
|
+
{ name: 'aura_install_component', description: 'Paste-ready setup for a free component: dependency list, setup steps, file map, fonts. Goes beyond the official Aura MCP, which only reads project source.', inputSchema: { type: 'object', properties: { id: {}, slug: { type: 'string' } }, additionalProperties: false } },
|
|
892
|
+
{ name: 'aura_install_skill', description: 'Save-and-load plan for a free skill: where to put SKILL.md per client plus upstream source.', inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'], additionalProperties: false } },
|
|
893
|
+
{ name: 'aura_use_design_system', description: 'Apply a free DESIGN.md system: token starter CSS plus copy order (tokens first, then markup).', inputSchema: { type: 'object', properties: { id: { type: 'string' }, slug: { type: 'string' } }, additionalProperties: false } },
|
|
894
|
+
{ name: 'aura_trending', description: 'What is new and popular across the free catalogue: top components, skills, assets, design systems in one call.', inputSchema: { type: 'object', properties: { limit: { type: 'number' } }, additionalProperties: false } },
|
|
895
|
+
{ name: 'aura_bundle', description: 'Bulk-fetch 2-8 component details in one call (ids or slugs). Per-item errors never fail the batch.', inputSchema: { type: 'object', properties: { ids: { type: 'array', items: {} }, slugs: { type: 'array', items: { type: 'string' } } }, additionalProperties: false } },
|
|
896
|
+
{ name: 'aura_scaffold_page', description: 'One ordered page build: DESIGN.md tokens.css + system preview + component markup in dependency order, combined deps + files[]. Merges install_* + use_* in a single turn.', inputSchema: { type: 'object', properties: { goal: { type: 'string' }, system: { type: 'string' }, components: { type: 'array', items: {} } }, required: ['goal'], additionalProperties: false } },
|
|
897
|
+
{ name: 'aura_related', description: 'More-like-this: 3 related items for a component/skill/design-system by tag + text overlap. Discovery never dead-ends.', inputSchema: { type: 'object', properties: { kind: { type: 'string', enum: ['components', 'skills', 'design_systems'] }, id: {} }, required: ['kind', 'id'], additionalProperties: false } },
|
|
898
|
+
{ name: 'aura_bulk_fetch', description: 'Bulk-fetch 2-8 details in one call for components, design_systems, or assets. Alias-friendly name for aura_bundle. Per-item errors never fail the batch.', inputSchema: { type: 'object', properties: { kind: { type: 'string', enum: ['components', 'design_systems', 'assets'] }, ids: { type: 'array', items: {} } }, required: ['kind', 'ids'], additionalProperties: false } },
|
|
899
|
+
{ name: 'aura_install_asset', description: 'Legal drop-in plan for an asset: direct download URL, preview URL, license (all-rights-reserved per Aura Terms §4 — check page before commercial use), suggested file path.', inputSchema: { type: 'object', properties: { id: { type: 'number' } }, required: ['id'], additionalProperties: false } },
|
|
900
|
+
{ name: 'aura_categories', description: 'The 13 component categories with live free counts. Pick one, then search within it.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
|
|
901
|
+
];
|
|
902
|
+
function createHandlers(catalog) {
|
|
903
|
+
const free = { freeOnly: true };
|
|
904
|
+
return {
|
|
905
|
+
aura_status: async () => textResult(await catalog.getStatus()),
|
|
906
|
+
aura_search_components: async (a) => { a = a || {}; return textResult(await catalog.searchCatalog('components', {
|
|
907
|
+
query: toolFn_str(a.query, 'query'), tag: toolFn_cat(a.category), freeOnly: true, theme: theme(a.theme),
|
|
908
|
+
negative_theme: a.negative_theme === undefined ? undefined : theme(a.negative_theme),
|
|
909
|
+
min_views: toolFn_num(a.min_views, 'min_views'),
|
|
910
|
+
sort: toolFn_sort(a.sort), limit: toolFn_num(a.limit, 'limit'), offset: toolFn_num(a.offset, 'offset'),
|
|
911
|
+
})); },
|
|
912
|
+
aura_get_component: async (a) => { a = a || {}; const id = a.id !== undefined ? a.id : a.slug;
|
|
913
|
+
if (id === undefined || (typeof id !== 'string' && typeof id !== 'number')) toolFn_bad('provide id (number) or slug (string)');
|
|
914
|
+
const chunk = a.chunk === undefined ? 0 : toolFn_num(a.chunk, 'chunk');
|
|
915
|
+
return textResult(await catalog.getItem('components', id, { chunk })); },
|
|
916
|
+
aura_search_skills: async (a) => { a = a || {}; return textResult(await catalog.searchCatalog('skills', {
|
|
917
|
+
query: toolFn_str(a.query, 'query'), sort: toolFn_sort(a.sort), limit: toolFn_num(a.limit, 'limit'), offset: toolFn_num(a.offset, 'offset'),
|
|
918
|
+
})); },
|
|
919
|
+
aura_get_skill: async (a) => { a = a || {}; if (typeof a.id !== 'string' || !a.id) toolFn_bad('id (string) is required');
|
|
920
|
+
const chunk = a.chunk === undefined ? 0 : toolFn_num(a.chunk, 'chunk');
|
|
921
|
+
return textResult(await catalog.getItem('skills', a.id, { chunk })); },
|
|
922
|
+
aura_search_assets: async (a) => { a = a || {}; const co = a.commercial_ok === undefined ? undefined : toolFn_bool(a.commercial_ok, 'commercial_ok');
|
|
923
|
+
const r = await catalog.searchCatalog('assets', { query: toolFn_str(a.query, 'query'), mediaType: a.mediaType === undefined ? undefined : (a.mediaType === 'image' || a.mediaType === 'video' ? a.mediaType : toolFn_bad('mediaType must be image|video')), freeOnly: true, sort: toolFn_sort(a.sort), limit: toolFn_num(a.limit, 'limit'), offset: toolFn_num(a.offset, 'offset') });
|
|
924
|
+
// Gap 5: commercial_ok filter (all-rights-reserved per Aura Terms S4).
|
|
925
|
+
// commercial_ok:true returns [] with explanation, never silence.
|
|
926
|
+
if (co === true) return textResult({ ...r, items: [], commercial_ok: false, commercial_note: 'All Aura catalogue assets are all-rights-reserved per Aura Terms S4. Preview/personal use via page_url; commercial reuse needs Aura permission.' });
|
|
927
|
+
if (co === true) return textResult({ ...r, items: [], commercial_ok: false, commercial_note: 'x' });
|
|
928
|
+
if (co === true) return textResult({ ...r, items: [], commercial_ok: false, commercial_note: 'All Aura catalogue assets are all-rights-reserved per Aura Terms S4.' });
|
|
929
|
+
return textResult(r); },
|
|
930
|
+
aura_search_design_systems: async (a) => { a = a || {}; return textResult(await catalog.searchCatalog('design_systems', {
|
|
931
|
+
query: toolFn_str(a.query, 'query'), sort: toolFn_sort(a.sort), limit: toolFn_num(a.limit, 'limit'), offset: toolFn_num(a.offset, 'offset'),
|
|
932
|
+
})); },
|
|
933
|
+
aura_get_design_system: async (a) => { a = a || {}; const id = a.id !== undefined ? a.id : a.slug;
|
|
934
|
+
if (typeof id !== 'string' || !id) toolFn_bad('provide id or slug (string)');
|
|
935
|
+
const chunk = a.chunk === undefined ? 0 : toolFn_num(a.chunk, 'chunk');
|
|
936
|
+
return textResult(await catalog.getItem('design_systems', id, { chunk })); },
|
|
937
|
+
aura_search_all: async (a) => { a = a || {}; const query = toolFn_str(a.query, 'query'), limit = toolFn_num(a.limit, 'limit') || 5;
|
|
938
|
+
const r = await Promise.all([
|
|
939
|
+
catalog.searchCatalog('components', { query, freeOnly: true, limit }),
|
|
940
|
+
catalog.searchCatalog('skills', { query, limit }),
|
|
941
|
+
catalog.searchCatalog('assets', { query, freeOnly: true, limit }),
|
|
942
|
+
catalog.searchCatalog('design_systems', { query, limit }),
|
|
943
|
+
]);
|
|
944
|
+
return textResult({ query: query || null, components: r[0], skills: r[1], assets: r[2], design_systems: r[3] }); },
|
|
945
|
+
aura_recommend: async (a) => { a = a || {}; if (typeof a.goal !== 'string' || !a.goal.trim()) toolFn_bad('goal (string) is required');
|
|
946
|
+
// Gap 1: recommend hard-filters theme (not just reranks) — starter kits must not
|
|
947
|
+
// mix light rows into a dark ask. min_views gates noise; both are explicit params.
|
|
948
|
+
const themeV = a.theme === undefined ? undefined : theme(a.theme);
|
|
949
|
+
const negative_themeV = a.negative_theme === undefined ? undefined : theme(a.negative_theme);
|
|
950
|
+
const min_views = toolFn_num(a.min_views, 'min_views');
|
|
951
|
+
const r = await Promise.all([
|
|
952
|
+
catalog.searchCatalog('components', { query: a.goal, freeOnly: true, limit: 8, theme: themeV, negative_theme: negative_themeV, min_views }),
|
|
953
|
+
catalog.searchCatalog('skills', { query: a.goal, limit: 5 }),
|
|
954
|
+
catalog.searchCatalog('assets', { query: a.goal, freeOnly: true, limit: 3 }),
|
|
955
|
+
catalog.searchCatalog('design_systems', { query: a.goal, limit: 3 }),
|
|
956
|
+
]);
|
|
957
|
+
r[0].items = r[0].items.slice(0, 5);
|
|
958
|
+
const pick = (x) => (x.items || []).slice(0, 3).map((i) => ({ title: i.title, page_url: i.page_url }));
|
|
959
|
+
return textResult({ goal: a.goal, freeOnly: true,
|
|
960
|
+
rationale: 'Top free catalogue hits per surface, ranked by Aura views. Open the page_url previews, then fetch full source or content for finalists.',
|
|
961
|
+
components: r[0], skills: r[1], assets: r[2], design_systems: r[3],
|
|
962
|
+
starter_kit: { components: pick(r[0]), skills: pick(r[1]), assets: pick(r[2]), design_systems: pick(r[3]) } }); },
|
|
963
|
+
aura_install_component: async (a) => { a = a || {}; const id = a.id !== undefined ? a.id : a.slug;
|
|
964
|
+
if (id === undefined || (typeof id !== 'string' && typeof id !== 'number')) toolFn_bad('provide id (number) or slug (string)');
|
|
965
|
+
const got = await catalog.getItem('components', id);
|
|
966
|
+
if (got.item && got.item.premium !== false) toolFn_bad('that component is Pro (paid). Search with freeOnly or pick a free one.');
|
|
967
|
+
return textResult({ item: got.item, install: installGuide('components', got.item) }); },
|
|
968
|
+
aura_install_skill: async (a) => { a = a || {}; if (typeof a.id !== 'string' || !a.id) toolFn_bad('id (string) is required');
|
|
969
|
+
const got = await catalog.getItem('skills', a.id);
|
|
970
|
+
return textResult({ item: got.item, install: installGuide('skills', got.item) }); },
|
|
971
|
+
aura_use_design_system: async (a) => { a = a || {}; const id = a.id !== undefined ? a.id : a.slug;
|
|
972
|
+
if (typeof id !== 'string' || !id) toolFn_bad('provide id or slug (string)');
|
|
973
|
+
const got = await catalog.getItem('design_systems', id);
|
|
974
|
+
return textResult({ item: got.item, install: installGuide('design_systems', got.item), tokens: tokenHints(got.item.content || '', got.item.preview_html || '') }); },
|
|
975
|
+
aura_trending: async (a) => { a = a || {}; const limit = toolFn_num(a.limit, 'limit') || 5;
|
|
976
|
+
// Gap 2: 30d rolling window + editorial picks. Components at 90d/views are noise
|
|
977
|
+
// (total 2, views 0-5); skills/assets already work and keep their own signal.
|
|
978
|
+
const windowDays = a.window_days === undefined ? 30 : toolFn_num(a.window_days, 'window_days');
|
|
979
|
+
const r = await Promise.all([
|
|
980
|
+
catalog.searchCatalog('components', { freeOnly: true, sort: 'trending', limit, window_days: windowDays }),
|
|
981
|
+
catalog.searchCatalog('skills', { sort: 'trending', limit }),
|
|
982
|
+
catalog.searchCatalog('assets', { freeOnly: true, sort: 'trending', limit }),
|
|
983
|
+
catalog.searchCatalog('design_systems', { sort: 'trending', limit }),
|
|
984
|
+
]);
|
|
985
|
+
const picks = await catalog.editorialPicks(Math.min(limit, 5)).catch(() => []);
|
|
986
|
+
return textResult({ window: 'last ' + windowDays + ' days by views', window_note: 'Components at 90d/views are low-signal (total 2, views 0-5): see editorial_picks for human-grade component leads; skills/assets trending is organic.', editorial_picks: picks, components: r[0], skills: r[1], assets: r[2], design_systems: r[3] });},
|
|
987
|
+
aura_categories: async () => textResult({ categories: await catalog.categoryCounts() }),
|
|
988
|
+
aura_bundle: async (a) => { a = a || {}; const ids = idList(a.ids, 'ids') || idList(a.slugs, 'slugs');
|
|
989
|
+
if (!ids || !ids.length) toolFn_bad('provide ids (array of 1-8 numbers/strings) or slugs (array of strings)');
|
|
990
|
+
return textResult({ kind: 'components', results: await catalog.bundleItems('components', ids) }); },
|
|
991
|
+
aura_related: async (a) => { a = a || {}; const k = a.kind;
|
|
992
|
+
if (k !== 'components' && k !== 'skills' && k !== 'design_systems') toolFn_bad('kind must be components|skills|design_systems');
|
|
993
|
+
const id = a.id; if (typeof id !== 'string' && typeof id !== 'number') toolFn_bad('id is required');
|
|
994
|
+
return textResult(await catalog.relatedItems(k, id, 3)); },
|
|
995
|
+
aura_install_asset: async (a) => { a = a || {}; if (typeof a.id !== 'number') toolFn_bad('id (number) is required');
|
|
996
|
+
const got = await catalog.getItem('assets', a.id);
|
|
997
|
+
const it = got.item; const fx = (it.facets || {});
|
|
998
|
+
return textResult({ item: it, install: { kind: 'assets', title: it.title, page_url: it.page_url, license: fx.license || 'all-rights-reserved (Aura Terms §4)', terms_url: 'https://www.aura.build/terms', download: fx.download || null, preview: fx.preview || null, files: [{ path: 'assets/' + it.id + '-' + String(it.title || 'asset').toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 40) + '.jpg', contains: 'downloaded original' }], steps: ['Check the license on the Aura asset page before commercial use — this server reports unknown, never assumes free-to-sell.', 'Download the download URL into the suggested path.', 'Use the preview URL for <img> srcset while drafting.'] } }); },
|
|
999
|
+
aura_bulk_fetch: async (a) => { a = a || {}; const kind = a.kind;
|
|
1000
|
+
if (kind !== 'components' && kind !== 'design_systems' && kind !== 'assets') toolFn_bad('kind must be components|design_systems|assets');
|
|
1001
|
+
const ids = idList(a.ids, 'ids');
|
|
1002
|
+
if (!ids || !ids.length) toolFn_bad('provide ids (array of 1-8)');
|
|
1003
|
+
return textResult({ kind, results: await catalog.bundleItems(kind, ids) }); },
|
|
1004
|
+
aura_scaffold_page: async (a) => { a = a || {}; if (typeof a.goal !== 'string' || !a.goal.trim()) toolFn_bad('goal (string) is required');
|
|
1005
|
+
const sysRef = (typeof a.system === 'string' && a.system) ? a.system : null;
|
|
1006
|
+
const compRefs = idList(a.components, 'components') || [];
|
|
1007
|
+
const [sys, comps] = await Promise.all([
|
|
1008
|
+
sysRef ? catalog.getItem('design_systems', sysRef).catch(() => null) : catalog.searchCatalog('design_systems', { query: a.goal, limit: 1 }).then((r) => (r.items[0] ? { item: r.items[0] } : null)),
|
|
1009
|
+
compRefs.length ? catalog.bundleItems('components', compRefs).then((rs) => rs.filter((x) => x.ok).map((x) => x.item)) : catalog.searchCatalog('components', { query: a.goal, freeOnly: true, limit: 3 }).then((r) => r.items),
|
|
1010
|
+
]);
|
|
1011
|
+
const sysItem = sys && sys.item ? sys.item : null;
|
|
1012
|
+
const toks = sysItem ? tokenHints(sysItem.content || '') : { tokens: {}, css: '' };
|
|
1013
|
+
const guides = (comps || []).map((c) => installGuide('components', c));
|
|
1014
|
+
const deps = []; const seen = new Set();
|
|
1015
|
+
for (const g of guides) for (const d of (g.deps || [])) if (!seen.has(d.name)) { seen.add(d.name); deps.push(d); }
|
|
1016
|
+
const files = [{ path: 'styles/tokens.css', contains: 'tokens.css from use_design_system' }];
|
|
1017
|
+
if (sysItem) files.push({ path: 'DESIGN.md', contains: 'system rules' });
|
|
1018
|
+
for (const g of guides) for (const f of (g.files || [])) files.push(f);
|
|
1019
|
+
return textResult({ goal: a.goal, order: ['1 tokens.css', '2 system preview_html', '3 component markup in listed order'], tokens_css: toks.css || '', system: sysItem, components: comps || [], guides, combined_deps: deps, files }); },
|
|
1020
|
+
};
|
|
1021
|
+
};
|
|
858
1022
|
|
|
859
1023
|
// ---- server.mjs (main) ----
|
|
860
1024
|
// Module: server — composition root. Wires config -> fetcher -> catalog ->
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aura-components-mcp",
|
|
3
|
-
"version": "1.6.
|
|
4
|
-
"description": "Free MCP server for the Aura public catalogue: 2,495 components, 187 skills, 30k assets, 725 design systems. Never-empty search, bulk fetch, scaffold pages, install plans. No login.",
|
|
3
|
+
"version": "1.6.2",
|
|
5
4
|
"description": "Free-only MCP server for the Aura (aura.build) public catalogue: 2,495 components, 187 skills, 30,688 assets, 725 design systems. No login. Built by swastiksingh-dev.",
|
|
6
5
|
"type": "module",
|
|
7
6
|
"license": "MIT",
|