@aicanvas/mcp 0.1.0 → 0.2.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 +18 -8
- package/dist/index.js +244 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,18 +13,23 @@ AI: [edits the file directly]
|
|
|
13
13
|
|
|
14
14
|
## What it exposes
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Eight tools, derived from the live [aicanvas.me](https://aicanvas.me) registry. Adding a component to the website auto-updates the MCP within minutes. The registry holds 75 standalone components, plus the Andromeda design system with its own components and templates.
|
|
17
17
|
|
|
18
18
|
| Tool | Purpose |
|
|
19
19
|
|---|---|
|
|
20
20
|
| `list_categories` | Show every category with component counts. Orient before drilling in. |
|
|
21
|
-
| `list_components` | Browse components, optionally filtered by category. Paginated. |
|
|
22
|
-
| `search_components` | Fuzzy keyword search across slugs, names, descriptions, categories, tags. Ranked results. |
|
|
23
|
-
| `get_component` | Full metadata
|
|
24
|
-
| `get_install_command` | Get the `npx shadcn add @aicanvas/<slug>` command for
|
|
21
|
+
| `list_components` | Browse standalone components, optionally filtered by category. Paginated. |
|
|
22
|
+
| `search_components` | Fuzzy keyword search across standalone AND design-system components (slugs, names, descriptions, categories, tags). Ranked results. |
|
|
23
|
+
| `get_component` | Full metadata plus complete `.tsx` source code for one component. Resolves both standalone slugs and design-system component slugs (e.g. `andromeda-heat-grid`). |
|
|
24
|
+
| `get_install_command` | Get the `npx shadcn add @aicanvas/<slug>` command for any standalone or design-system component. |
|
|
25
|
+
| `list_systems` | List the design systems available (e.g. Andromeda), with component and template counts. |
|
|
26
|
+
| `get_system` | Every file for a whole design system, plus its shared tokens, its component install commands, and the dependency chain. |
|
|
27
|
+
| `get_template` | Every file for a single template (a ready-made composition), with the registry dependencies it pulls in. |
|
|
25
28
|
|
|
26
29
|
All tools are read-only. Nothing is mutated on AI Canvas's side.
|
|
27
30
|
|
|
31
|
+
Standalone components and design-system components are kept in separate buckets. `list_components` and `list_categories` cover the 75 standalones only, so the catalog stays clean. Design-system components are reached through `list_systems` / `get_system`, and are also resolvable by slug through `get_component`, `get_install_command`, and `search_components`.
|
|
32
|
+
|
|
28
33
|
## Setup
|
|
29
34
|
|
|
30
35
|
### Claude Desktop
|
|
@@ -100,7 +105,7 @@ Inside any of the AI editors above, ask:
|
|
|
100
105
|
|
|
101
106
|
> "What categories of components does AI Canvas have?"
|
|
102
107
|
|
|
103
|
-
You should see all 11 categories with counts. If you don't, check the host's MCP logs
|
|
108
|
+
You should see all 11 categories with counts. If you don't, check the host's MCP logs. Typically a "Failed to spawn process" or network error means npx couldn't fetch the package, or the host wasn't restarted after editing the config.
|
|
104
109
|
|
|
105
110
|
To inspect the server outside an editor:
|
|
106
111
|
|
|
@@ -115,10 +120,15 @@ This opens a browser UI where you can call each tool by hand.
|
|
|
115
120
|
| Env var | Default | What it does |
|
|
116
121
|
|---|---|---|
|
|
117
122
|
| `AICANVAS_REGISTRY_BASE` | `https://aicanvas.me/r` | Registry root URL. Override only for local development against a self-hosted mirror. |
|
|
123
|
+
| `AICANVAS_TOKEN` | _(none)_ | Optional per-account token. The website bakes it into the MCP config it gives you. It identifies your account so pulls count against the right quota and unlock premium content. Sent as a `Bearer` token on every registry request. Absent means anonymous, which is subject to the daily free limit. |
|
|
124
|
+
|
|
125
|
+
### Daily limits and premium content
|
|
126
|
+
|
|
127
|
+
Anonymous use is capped by a daily free limit, and some content (such as full design systems) is premium. When you hit the limit or request gated content without entitlement, a source fetch returns HTTP 402 and the tool reports a clear message: upgrade at [aicanvas.me/pricing](https://aicanvas.me/pricing), set `AICANVAS_TOKEN` to use your account, and update with `npx @aicanvas/mcp@latest`. Metadata browsing (`list_categories`, `list_components`, `search_components`, `list_systems`) is not gated. The 402 only affects source fetches (`get_component`, `get_system`, `get_template`).
|
|
118
128
|
|
|
119
129
|
## How the registry stays fresh
|
|
120
130
|
|
|
121
|
-
The MCP fetches `https://aicanvas.me/r/aicanvas-mcp.json` on first use and caches it for 5 minutes. New components on AI Canvas appear in the MCP automatically
|
|
131
|
+
The MCP fetches `https://aicanvas.me/r/aicanvas-mcp.json` on first use and caches it for 5 minutes. New components on AI Canvas appear in the MCP automatically, no package update required.
|
|
122
132
|
|
|
123
133
|
The full source code for each component is fetched on demand from `/r/<slug>.json` (the same shadcn registry items the CLI installs).
|
|
124
134
|
|
|
@@ -141,4 +151,4 @@ Edit `src/index.ts`, run `npm run build`, restart your AI editor.
|
|
|
141
151
|
|
|
142
152
|
## License
|
|
143
153
|
|
|
144
|
-
MIT
|
|
154
|
+
MIT, same as the AI Canvas project.
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* inspect, and install components without leaving the chat.
|
|
8
8
|
*
|
|
9
9
|
* Data flow:
|
|
10
|
-
* - aicanvas.me/r/aicanvas-mcp.json →
|
|
10
|
+
* - aicanvas.me/r/aicanvas-mcp.json → metadata for the 75 standalone
|
|
11
|
+
* components plus the Andromeda design system, its components, and templates
|
|
11
12
|
* - aicanvas.me/r/<slug>.json → full source code per component
|
|
12
13
|
*
|
|
13
14
|
* Both are static files, served from Vercel's CDN. Stateless server.
|
|
@@ -19,15 +20,26 @@ import { z } from 'zod';
|
|
|
19
20
|
const REGISTRY_BASE = process.env.AICANVAS_REGISTRY_BASE ?? 'https://aicanvas.me/r';
|
|
20
21
|
const META_URL = `${REGISTRY_BASE}/aicanvas-mcp.json`;
|
|
21
22
|
const META_TTL_MS = 5 * 60 * 1000; // 5 minutes — meta updates with deploys
|
|
23
|
+
const MCP_VERSION = '0.2.0';
|
|
24
|
+
const USER_AGENT = `aicanvas-mcp/${MCP_VERSION}`;
|
|
25
|
+
// Optional per-user token (the website bakes it into the copied MCP config).
|
|
26
|
+
// Identifies the account so pulls count against the right quota and unlock
|
|
27
|
+
// premium content. Absent = anonymous (subject to the daily free limit).
|
|
28
|
+
const USER_TOKEN = process.env.AICANVAS_TOKEN ?? '';
|
|
29
|
+
function registryHeaders() {
|
|
30
|
+
return {
|
|
31
|
+
Accept: 'application/json',
|
|
32
|
+
'User-Agent': USER_AGENT,
|
|
33
|
+
...(USER_TOKEN ? { Authorization: `Bearer ${USER_TOKEN}` } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
22
36
|
// ── Cached fetchers ──────────────────────────────────────────────────────────
|
|
23
37
|
let metaCache = null;
|
|
24
38
|
async function fetchMeta() {
|
|
25
39
|
if (metaCache && Date.now() - metaCache.fetchedAt < META_TTL_MS) {
|
|
26
40
|
return metaCache.data;
|
|
27
41
|
}
|
|
28
|
-
const res = await fetch(META_URL, {
|
|
29
|
-
headers: { Accept: 'application/json' },
|
|
30
|
-
});
|
|
42
|
+
const res = await fetch(META_URL, { headers: registryHeaders() });
|
|
31
43
|
if (!res.ok) {
|
|
32
44
|
throw new Error(`Failed to fetch AI Canvas registry meta from ${META_URL}: ${res.status} ${res.statusText}`);
|
|
33
45
|
}
|
|
@@ -37,7 +49,14 @@ async function fetchMeta() {
|
|
|
37
49
|
}
|
|
38
50
|
async function fetchComponentSource(slug) {
|
|
39
51
|
const url = `${REGISTRY_BASE}/${encodeURIComponent(slug)}.json`;
|
|
40
|
-
const res = await fetch(url, { headers:
|
|
52
|
+
const res = await fetch(url, { headers: registryHeaders() });
|
|
53
|
+
if (res.status === 402) {
|
|
54
|
+
const j = (await res.json().catch(() => ({})));
|
|
55
|
+
throw new Error(j.message ??
|
|
56
|
+
'This requires AI Canvas Premium, or you have hit the daily free limit. ' +
|
|
57
|
+
'Upgrade at https://aicanvas.me/pricing. Set AICANVAS_TOKEN to use your account, ' +
|
|
58
|
+
'and update with: npx @aicanvas/mcp@latest');
|
|
59
|
+
}
|
|
41
60
|
if (!res.ok) {
|
|
42
61
|
throw new Error(`Failed to fetch source for "${slug}" from ${url}: ${res.status} ${res.statusText}`);
|
|
43
62
|
}
|
|
@@ -84,6 +103,37 @@ function scoreMatch(query, c) {
|
|
|
84
103
|
function escapeRegExp(s) {
|
|
85
104
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
86
105
|
}
|
|
106
|
+
// Minimal searchable surface for design-system components — slug, name,
|
|
107
|
+
// description, and the system name. Mirrors scoreMatch's token logic so DS
|
|
108
|
+
// components rank alongside standalones in search results.
|
|
109
|
+
function scoreSystemComponent(query, c) {
|
|
110
|
+
const q = normalize(query);
|
|
111
|
+
if (!q)
|
|
112
|
+
return 0;
|
|
113
|
+
const tokens = q.split(/\s+/).filter((t) => t.length > 2);
|
|
114
|
+
if (tokens.length === 0)
|
|
115
|
+
return 0;
|
|
116
|
+
const fields = [
|
|
117
|
+
{ text: normalize(c.slug), weight: 5 },
|
|
118
|
+
{ text: normalize(c.name), weight: 4 },
|
|
119
|
+
{ text: normalize(c.description), weight: 1 },
|
|
120
|
+
{ text: normalize(c.system), weight: 2 },
|
|
121
|
+
];
|
|
122
|
+
let score = 0;
|
|
123
|
+
for (const tok of tokens) {
|
|
124
|
+
for (const f of fields) {
|
|
125
|
+
if (!f.text)
|
|
126
|
+
continue;
|
|
127
|
+
if (new RegExp(`\\b${escapeRegExp(tok)}\\b`).test(f.text)) {
|
|
128
|
+
score += f.weight * 2;
|
|
129
|
+
}
|
|
130
|
+
else if (f.text.includes(tok)) {
|
|
131
|
+
score += f.weight;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return score;
|
|
136
|
+
}
|
|
87
137
|
function asTextContent(value) {
|
|
88
138
|
return {
|
|
89
139
|
type: 'text',
|
|
@@ -97,7 +147,7 @@ function errorResult(message) {
|
|
|
97
147
|
};
|
|
98
148
|
}
|
|
99
149
|
// ── Server ───────────────────────────────────────────────────────────────────
|
|
100
|
-
const server = new McpServer({ name: 'aicanvas-mcp', version:
|
|
150
|
+
const server = new McpServer({ name: 'aicanvas-mcp', version: MCP_VERSION }, { capabilities: { tools: {}, logging: {} } });
|
|
101
151
|
// ── Tool: list_categories ────────────────────────────────────────────────────
|
|
102
152
|
server.registerTool('list_categories', {
|
|
103
153
|
title: 'List AI Canvas categories',
|
|
@@ -203,14 +253,25 @@ server.registerTool('search_components', {
|
|
|
203
253
|
}, async ({ query, limit = 10 }) => {
|
|
204
254
|
try {
|
|
205
255
|
const meta = await fetchMeta();
|
|
206
|
-
|
|
207
|
-
|
|
256
|
+
// Rank standalones AND design-system components together so DS slugs
|
|
257
|
+
// (e.g. "andromeda-heat-grid") surface in search. Each result object stays
|
|
258
|
+
// usable: standalones keep their full shape; DS components carry their
|
|
259
|
+
// metadata (slug, name, description, system, install command, …).
|
|
260
|
+
const standaloneRanked = meta.components.map((c) => ({
|
|
261
|
+
item: c,
|
|
262
|
+
score: scoreMatch(query, c),
|
|
263
|
+
}));
|
|
264
|
+
const systemRanked = (meta.systemComponents ?? []).map((c) => ({
|
|
265
|
+
item: c,
|
|
266
|
+
score: scoreSystemComponent(query, c),
|
|
267
|
+
}));
|
|
268
|
+
const ranked = [...standaloneRanked, ...systemRanked]
|
|
208
269
|
.filter((r) => r.score > 0)
|
|
209
270
|
.sort((a, b) => b.score - a.score)
|
|
210
271
|
.slice(0, limit)
|
|
211
|
-
.map((r) => r.
|
|
272
|
+
.map((r) => r.item);
|
|
212
273
|
const summary = ranked.length === 0
|
|
213
|
-
? `No matches for "${query}". Try \`list_categories\` to browse
|
|
274
|
+
? `No matches for "${query}". Try \`list_categories\` to browse standalones, or \`list_systems\` for design systems.`
|
|
214
275
|
: `${ranked.length} match${ranked.length === 1 ? '' : 'es'} for "${query}"`;
|
|
215
276
|
return {
|
|
216
277
|
content: [asTextContent(summary + '\n\n' + JSON.stringify(ranked, null, 2))],
|
|
@@ -236,12 +297,41 @@ server.registerTool('get_component', {
|
|
|
236
297
|
try {
|
|
237
298
|
const meta = await fetchMeta();
|
|
238
299
|
const component = meta.components.find((c) => c.slug === slug);
|
|
239
|
-
|
|
240
|
-
|
|
300
|
+
// Fall back to design-system components (kept out of components[]).
|
|
301
|
+
const systemComponent = component
|
|
302
|
+
? undefined
|
|
303
|
+
: (meta.systemComponents ?? []).find((c) => c.slug === slug);
|
|
304
|
+
if (!component && !systemComponent) {
|
|
305
|
+
return errorResult(`No component found with slug "${slug}". Use \`search_components\` or \`list_components\` to find standalone slugs, ` +
|
|
306
|
+
`or \`list_systems\` / \`get_system\` for design-system components (e.g. "andromeda-heat-grid").`);
|
|
241
307
|
}
|
|
242
308
|
const source = await fetchComponentSource(slug);
|
|
243
309
|
const code = source.files[0]?.content ?? '';
|
|
244
310
|
const filePath = source.files[0]?.path ?? `components/aicanvas/${slug}.tsx`;
|
|
311
|
+
if (systemComponent) {
|
|
312
|
+
const result = { ...systemComponent, filePath, code };
|
|
313
|
+
return {
|
|
314
|
+
content: [
|
|
315
|
+
asTextContent([
|
|
316
|
+
`# ${systemComponent.name}`,
|
|
317
|
+
'',
|
|
318
|
+
systemComponent.description,
|
|
319
|
+
'',
|
|
320
|
+
`Design system: ${systemComponent.system}`,
|
|
321
|
+
`Dependencies: ${systemComponent.dependencies.join(', ') || '(none)'}`,
|
|
322
|
+
`Registry dependencies: ${(systemComponent.registryDependencies ?? []).join(', ') || '(none)'}`,
|
|
323
|
+
`Install: ${systemComponent.installCommand}`,
|
|
324
|
+
`Homepage: ${systemComponent.homepageUrl}`,
|
|
325
|
+
'',
|
|
326
|
+
'Note: a manual (non-CLI) write must ALSO install the shared tokens this component depends on (see registry dependencies above and `get_system`). The CLI install resolves them automatically.',
|
|
327
|
+
'',
|
|
328
|
+
`--- ${filePath} ---`,
|
|
329
|
+
code,
|
|
330
|
+
].join('\n')),
|
|
331
|
+
],
|
|
332
|
+
structuredContent: result,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
245
335
|
const result = {
|
|
246
336
|
...component,
|
|
247
337
|
filePath,
|
|
@@ -284,9 +374,11 @@ server.registerTool('get_install_command', {
|
|
|
284
374
|
}, async ({ slug }) => {
|
|
285
375
|
try {
|
|
286
376
|
const meta = await fetchMeta();
|
|
287
|
-
const component = meta.components.find((c) => c.slug === slug)
|
|
377
|
+
const component = meta.components.find((c) => c.slug === slug) ??
|
|
378
|
+
(meta.systemComponents ?? []).find((c) => c.slug === slug);
|
|
288
379
|
if (!component) {
|
|
289
|
-
return errorResult(`No component found with slug "${slug}". Use \`search_components\` to find
|
|
380
|
+
return errorResult(`No component found with slug "${slug}". Use \`search_components\` to find standalone slugs, ` +
|
|
381
|
+
`or \`list_systems\` / \`get_system\` for design-system components.`);
|
|
290
382
|
}
|
|
291
383
|
const out = {
|
|
292
384
|
slug: component.slug,
|
|
@@ -315,6 +407,144 @@ server.registerTool('get_install_command', {
|
|
|
315
407
|
return errorResult(err instanceof Error ? err.message : String(err));
|
|
316
408
|
}
|
|
317
409
|
});
|
|
410
|
+
// ── Tool: list_systems ───────────────────────────────────────────────────────
|
|
411
|
+
server.registerTool('list_systems', {
|
|
412
|
+
title: 'List AI Canvas design systems',
|
|
413
|
+
description: 'Return every design system available on AI Canvas. A design system is a coordinated set of components plus shared tokens and utilities — installable in one CLI command. Use when the user asks about "themes", "design systems", or wants more than a single component.',
|
|
414
|
+
inputSchema: {},
|
|
415
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
416
|
+
}, async () => {
|
|
417
|
+
try {
|
|
418
|
+
const meta = await fetchMeta();
|
|
419
|
+
const systems = meta.systems ?? [];
|
|
420
|
+
if (systems.length === 0) {
|
|
421
|
+
return {
|
|
422
|
+
content: [asTextContent('No design systems available in this registry build.')],
|
|
423
|
+
structuredContent: { systems: [] },
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const lines = [
|
|
427
|
+
`${systems.length} design system${systems.length === 1 ? '' : 's'} on AI Canvas:`,
|
|
428
|
+
'',
|
|
429
|
+
...systems.map((s) => ` ${s.name.padEnd(16)} ${String(s.componentCount).padStart(2)} components, ${s.templateSlugs.length} templates`),
|
|
430
|
+
'',
|
|
431
|
+
'Use `get_system` to fetch the full source of a system, or `get_template` for a single composition.',
|
|
432
|
+
];
|
|
433
|
+
return {
|
|
434
|
+
content: [asTextContent(lines.join('\n'))],
|
|
435
|
+
structuredContent: { systems },
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
// ── Tool: get_system ─────────────────────────────────────────────────────────
|
|
443
|
+
server.registerTool('get_system', {
|
|
444
|
+
title: 'Get a complete AI Canvas design system (every file)',
|
|
445
|
+
description: 'Return all files for a design system in a single response — every component, plus shared tokens and utilities — ready to write into the user\'s project. Use when the user wants to adopt a whole system rather than pick individual components.',
|
|
446
|
+
inputSchema: {
|
|
447
|
+
slug: z
|
|
448
|
+
.string()
|
|
449
|
+
.min(1)
|
|
450
|
+
.describe('Design system slug, e.g. "andromeda". Use `list_systems` to discover.'),
|
|
451
|
+
},
|
|
452
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
453
|
+
}, async ({ slug }) => {
|
|
454
|
+
try {
|
|
455
|
+
const meta = await fetchMeta();
|
|
456
|
+
const system = (meta.systems ?? []).find((s) => s.slug === slug);
|
|
457
|
+
if (!system) {
|
|
458
|
+
return errorResult(`No design system found with slug "${slug}". Use \`list_systems\` to see what's available.`);
|
|
459
|
+
}
|
|
460
|
+
const item = await fetchComponentSource(slug);
|
|
461
|
+
// The system's own design-system components, with their per-component
|
|
462
|
+
// install commands — so an agent can install (or hand-write) any subset.
|
|
463
|
+
const components = (meta.systemComponents ?? []).filter((c) => c.system === system.slug ||
|
|
464
|
+
(system.componentSlugs ?? []).includes(c.slug));
|
|
465
|
+
const summary = [
|
|
466
|
+
`# ${system.name} design system`,
|
|
467
|
+
'',
|
|
468
|
+
system.description,
|
|
469
|
+
'',
|
|
470
|
+
`Files: ${item.files.length}`,
|
|
471
|
+
`Dependencies: ${system.dependencies.join(', ') || '(none)'}`,
|
|
472
|
+
`Registry dependencies: ${(system.registryDependencies ?? []).join(', ') || '(none)'}`,
|
|
473
|
+
`Install (whole system): ${system.installCommand}`,
|
|
474
|
+
...(system.tokensInstallCommand
|
|
475
|
+
? [`Install (shared tokens only): ${system.tokensInstallCommand}`]
|
|
476
|
+
: []),
|
|
477
|
+
...(system.tokensSourceUrl ? [`Tokens source: ${system.tokensSourceUrl}`] : []),
|
|
478
|
+
`Homepage: ${system.homepageUrl}`,
|
|
479
|
+
'',
|
|
480
|
+
'Note: the shared tokens ship as a SEPARATE registry dependency. For a manual (non-CLI) file write you must ALSO install the tokens (see "Install (shared tokens only)" above); the CLI resolves them automatically.',
|
|
481
|
+
'',
|
|
482
|
+
`--- components (${components.length}) ---`,
|
|
483
|
+
...components.map((c) => ` ${c.slug.padEnd(28)} ${c.installCommand}`),
|
|
484
|
+
'',
|
|
485
|
+
`--- file index ---`,
|
|
486
|
+
...item.files.map((f) => ` ${f.path}`),
|
|
487
|
+
].join('\n');
|
|
488
|
+
return {
|
|
489
|
+
content: [asTextContent(summary)],
|
|
490
|
+
structuredContent: {
|
|
491
|
+
...system,
|
|
492
|
+
components,
|
|
493
|
+
files: item.files,
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
catch (err) {
|
|
498
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
// ── Tool: get_template ───────────────────────────────────────────────────────
|
|
502
|
+
server.registerTool('get_template', {
|
|
503
|
+
title: 'Get a complete AI Canvas design-system template (every file)',
|
|
504
|
+
description: 'Return all files for a single template — the example composition plus every component it uses plus shared tokens. Use for "I want exactly this dashboard" / "give me the entire mission-control screen". One CLI command installs all files.',
|
|
505
|
+
inputSchema: {
|
|
506
|
+
slug: z
|
|
507
|
+
.string()
|
|
508
|
+
.min(1)
|
|
509
|
+
.describe('Template slug, e.g. "andromeda-mission-control", "andromeda-exchange-terminal". Use `list_systems` then inspect templateSlugs to discover.'),
|
|
510
|
+
},
|
|
511
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
512
|
+
}, async ({ slug }) => {
|
|
513
|
+
try {
|
|
514
|
+
const meta = await fetchMeta();
|
|
515
|
+
const template = (meta.templates ?? []).find((t) => t.slug === slug);
|
|
516
|
+
if (!template) {
|
|
517
|
+
return errorResult(`No template found with slug "${slug}". Use \`list_systems\` to see available templates.`);
|
|
518
|
+
}
|
|
519
|
+
const item = await fetchComponentSource(slug);
|
|
520
|
+
const summary = [
|
|
521
|
+
`# ${template.name} (${template.system}${template.domain ? ` · ${template.domain}` : ''})`,
|
|
522
|
+
'',
|
|
523
|
+
template.description,
|
|
524
|
+
'',
|
|
525
|
+
`Files: ${item.files.length}`,
|
|
526
|
+
`Dependencies: ${template.dependencies.join(', ') || '(none)'}`,
|
|
527
|
+
`Registry dependencies: ${(template.registryDependencies ?? []).join(', ') || '(none)'}`,
|
|
528
|
+
`Install: ${template.installCommand}`,
|
|
529
|
+
`Preview: ${template.homepageUrl}`,
|
|
530
|
+
'',
|
|
531
|
+
'Note: a manual (non-CLI) file write must ALSO install the registry dependencies above (the base components + shared tokens) for the composition to build. The CLI install resolves them automatically.',
|
|
532
|
+
'',
|
|
533
|
+
`--- file index ---`,
|
|
534
|
+
...item.files.map((f) => ` ${f.path}`),
|
|
535
|
+
].join('\n');
|
|
536
|
+
return {
|
|
537
|
+
content: [asTextContent(summary)],
|
|
538
|
+
structuredContent: {
|
|
539
|
+
...template,
|
|
540
|
+
files: item.files,
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
catch (err) {
|
|
545
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
546
|
+
}
|
|
547
|
+
});
|
|
318
548
|
// ── Boot ─────────────────────────────────────────────────────────────────────
|
|
319
549
|
async function main() {
|
|
320
550
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED