@webspire/mcp 0.8.0 → 0.8.1
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 +22 -9
- package/css/webspire-components.css +176 -0
- package/data/registry.json +20001 -729
- package/dist/registration.d.ts +2 -2
- package/dist/registration.js +376 -285
- package/dist/search.d.ts +43 -1
- package/dist/search.js +778 -38
- package/dist/types.d.ts +15 -3
- package/package.json +5 -2
package/dist/registration.js
CHANGED
|
@@ -1,139 +1,146 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { searchSnippets } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { searchSnippets } from "./registry.js";
|
|
3
|
+
import { composePage, CONTENT_NEED_KEYS, CONTENT_NEED_LABEL, DOMAIN_KEYS, DOMAIN_LABEL, normalizeContentNeed, searchPatterns, TONE_KEYS, TONE_LABEL, UX_GOAL_KEYS, UX_GOAL_LABEL, } from "./search.js";
|
|
3
4
|
function formatSnippetBrief(s) {
|
|
4
5
|
return [
|
|
5
6
|
`**${s.title}** (${s.id})`,
|
|
6
7
|
` ${s.description}`,
|
|
7
|
-
` Category: ${s.category} | Tags: ${s.tags.join(
|
|
8
|
-
s.darkMode ?
|
|
9
|
-
s.responsive ?
|
|
10
|
-
s.meta.useCases.length > 0
|
|
8
|
+
` Category: ${s.category} | Tags: ${s.tags.join(", ")}`,
|
|
9
|
+
s.darkMode ? " Dark mode: yes" : "",
|
|
10
|
+
s.responsive ? " Responsive: yes" : "",
|
|
11
|
+
s.meta.useCases.length > 0
|
|
12
|
+
? ` Use cases: ${s.meta.useCases.slice(0, 2).join("; ")}`
|
|
13
|
+
: "",
|
|
11
14
|
]
|
|
12
15
|
.filter(Boolean)
|
|
13
|
-
.join(
|
|
16
|
+
.join("\n");
|
|
14
17
|
}
|
|
15
18
|
function formatAccessibility(s) {
|
|
16
19
|
const a11y = s.meta.accessibility;
|
|
17
20
|
const features = [];
|
|
18
21
|
if (a11y.prefersReducedMotion)
|
|
19
|
-
features.push(
|
|
22
|
+
features.push("prefers-reduced-motion");
|
|
20
23
|
if (a11y.prefersColorScheme)
|
|
21
|
-
features.push(
|
|
24
|
+
features.push("prefers-color-scheme");
|
|
22
25
|
if (a11y.supportsCheck)
|
|
23
|
-
features.push(
|
|
26
|
+
features.push("@supports fallback");
|
|
24
27
|
if (a11y.ariaNotes?.length)
|
|
25
28
|
features.push(...a11y.ariaNotes);
|
|
26
|
-
return features.length > 0
|
|
29
|
+
return features.length > 0
|
|
30
|
+
? `## Accessibility\nRespects: ${features.join(", ")}`
|
|
31
|
+
: "";
|
|
27
32
|
}
|
|
28
33
|
function formatSnippetFull(s) {
|
|
29
34
|
const props = s.meta.customProperties
|
|
30
|
-
.map((p) => ` ${p}: ${s.meta.defaultValues?.[p] ??
|
|
31
|
-
.join(
|
|
35
|
+
.map((p) => ` ${p}: ${s.meta.defaultValues?.[p] ?? "unset"}`)
|
|
36
|
+
.join("\n");
|
|
32
37
|
return [
|
|
33
38
|
`# ${s.title}`,
|
|
34
|
-
|
|
39
|
+
"",
|
|
35
40
|
s.description,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
"",
|
|
42
|
+
"## Usage",
|
|
43
|
+
"```html",
|
|
39
44
|
s.meta.usageExample,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
"```",
|
|
46
|
+
"",
|
|
47
|
+
"## CSS",
|
|
48
|
+
"```css",
|
|
44
49
|
s.css,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
s.meta.customProperties.length > 0 ? `## Custom Properties\n${props}` :
|
|
48
|
-
s.meta.classes.length > 0 ? `## Classes\n${s.meta.classes.join(
|
|
50
|
+
"```",
|
|
51
|
+
"",
|
|
52
|
+
s.meta.customProperties.length > 0 ? `## Custom Properties\n${props}` : "",
|
|
53
|
+
s.meta.classes.length > 0 ? `## Classes\n${s.meta.classes.join(", ")}` : "",
|
|
49
54
|
formatAccessibility(s),
|
|
50
|
-
s.dependencies?.length
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
s.dependencies?.length
|
|
56
|
+
? `## Dependencies\n${s.dependencies.join(", ")}`
|
|
57
|
+
: "",
|
|
58
|
+
s.variants?.length ? `## Variants\n${s.variants.join(", ")}` : "",
|
|
59
|
+
"",
|
|
53
60
|
`Browser support: ${s.meta.browser}`,
|
|
54
61
|
`Size: ${s.meta.lines} lines, ${s.meta.bytes} bytes`,
|
|
55
|
-
s.darkMode ?
|
|
56
|
-
s.responsive ?
|
|
62
|
+
s.darkMode ? "Dark mode: supported" : "",
|
|
63
|
+
s.responsive ? "Responsive: yes" : "",
|
|
57
64
|
]
|
|
58
|
-
.filter((line) => line !==
|
|
59
|
-
.join(
|
|
65
|
+
.filter((line) => line !== "")
|
|
66
|
+
.join("\n");
|
|
60
67
|
}
|
|
61
68
|
function formatTemplateBrief(t) {
|
|
62
69
|
return [
|
|
63
70
|
`**${t.title}** (${t.id})`,
|
|
64
71
|
` ${t.summary}`,
|
|
65
72
|
` Category: ${t.category} | Style: ${t.style}`,
|
|
66
|
-
t.tags.length > 0 ? ` Tags: ${t.tags.join(
|
|
73
|
+
t.tags.length > 0 ? ` Tags: ${t.tags.join(", ")}` : "",
|
|
67
74
|
]
|
|
68
75
|
.filter(Boolean)
|
|
69
|
-
.join(
|
|
76
|
+
.join("\n");
|
|
70
77
|
}
|
|
71
78
|
function formatTemplateFull(t) {
|
|
72
79
|
return [
|
|
73
80
|
`# ${t.title}`,
|
|
74
|
-
|
|
81
|
+
"",
|
|
75
82
|
t.description ?? t.summary,
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
"",
|
|
84
|
+
"## Identity",
|
|
78
85
|
`ID: ${t.id}`,
|
|
79
86
|
`Category: ${t.category}`,
|
|
80
87
|
`Style: ${t.style}`,
|
|
81
|
-
|
|
82
|
-
t.tags.length > 0 ? `## Tags\n${t.tags.join(
|
|
83
|
-
t.sections.length > 0 ? `## Sections\n${t.sections.join(
|
|
84
|
-
t.patterns.length > 0 ? `## Uses Patterns\n${t.patterns.join(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
"",
|
|
89
|
+
t.tags.length > 0 ? `## Tags\n${t.tags.join(", ")}` : "",
|
|
90
|
+
t.sections.length > 0 ? `## Sections\n${t.sections.join(", ")}` : "",
|
|
91
|
+
t.patterns.length > 0 ? `## Uses Patterns\n${t.patterns.join(", ")}` : "",
|
|
92
|
+
"",
|
|
93
|
+
"## HTML",
|
|
94
|
+
"```html",
|
|
88
95
|
t.html,
|
|
89
|
-
|
|
96
|
+
"```",
|
|
90
97
|
]
|
|
91
|
-
.filter((line) => line !==
|
|
92
|
-
.join(
|
|
98
|
+
.filter((line) => line !== "")
|
|
99
|
+
.join("\n");
|
|
93
100
|
}
|
|
94
101
|
function formatPatternBrief(p) {
|
|
95
102
|
return [
|
|
96
103
|
`**${p.title}** (${p.id})`,
|
|
97
104
|
` ${p.summary}`,
|
|
98
105
|
` Family: ${p.family} | Tier: ${p.tier} | Kind: ${p.kind}`,
|
|
99
|
-
p.tags.length > 0 ? ` Tags: ${p.tags.join(
|
|
100
|
-
p.extends ? ` Extends: ${p.extends}` :
|
|
106
|
+
p.tags.length > 0 ? ` Tags: ${p.tags.join(", ")}` : "",
|
|
107
|
+
p.extends ? ` Extends: ${p.extends}` : "",
|
|
101
108
|
]
|
|
102
109
|
.filter(Boolean)
|
|
103
|
-
.join(
|
|
110
|
+
.join("\n");
|
|
104
111
|
}
|
|
105
112
|
function formatPatternFull(p) {
|
|
106
113
|
return [
|
|
107
114
|
`# ${p.title}`,
|
|
108
|
-
|
|
115
|
+
"",
|
|
109
116
|
p.description ?? p.summary,
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
"",
|
|
118
|
+
"## Identity",
|
|
112
119
|
`ID: ${p.id}`,
|
|
113
120
|
`Family: ${p.family}`,
|
|
114
121
|
`Tier: ${p.tier}`,
|
|
115
122
|
`Kind: ${p.kind}`,
|
|
116
|
-
p.extends ? `Extends: ${p.extends}` :
|
|
117
|
-
|
|
118
|
-
p.tags.length > 0 ? `## Tags\n${p.tags.join(
|
|
123
|
+
p.extends ? `Extends: ${p.extends}` : "",
|
|
124
|
+
"",
|
|
125
|
+
p.tags.length > 0 ? `## Tags\n${p.tags.join(", ")}` : "",
|
|
119
126
|
p.slots.length > 0
|
|
120
|
-
? `## Slots\n${p.slots.map((s) => `- ${s.name}${s.required ?
|
|
121
|
-
:
|
|
127
|
+
? `## Slots\n${p.slots.map((s) => `- ${s.name}${s.required ? " (required)" : ""}: ${s.description}`).join("\n")}`
|
|
128
|
+
: "",
|
|
122
129
|
p.props.length > 0
|
|
123
|
-
? `## Props\n${p.props.map((prop) => `- ${prop.name} (${prop.type}) default=${String(prop.default)}`).join(
|
|
124
|
-
:
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
? `## Props\n${p.props.map((prop) => `- ${prop.name} (${prop.type}) default=${String(prop.default)}`).join("\n")}`
|
|
131
|
+
: "",
|
|
132
|
+
"## HTML",
|
|
133
|
+
"```html",
|
|
127
134
|
p.html,
|
|
128
|
-
|
|
129
|
-
p.css ? `## CSS\n\`\`\`css\n${p.css}\n\`\`\`` :
|
|
130
|
-
p.js ? `## JS\n\`\`\`js\n${p.js}\n\`\`\`` :
|
|
135
|
+
"```",
|
|
136
|
+
p.css ? `## CSS\n\`\`\`css\n${p.css}\n\`\`\`` : "",
|
|
137
|
+
p.js ? `## JS\n\`\`\`js\n${p.js}\n\`\`\`` : "",
|
|
131
138
|
]
|
|
132
|
-
.filter((line) => line !==
|
|
133
|
-
.join(
|
|
139
|
+
.filter((line) => line !== "")
|
|
140
|
+
.join("\n");
|
|
134
141
|
}
|
|
135
142
|
export function registerToolsWithProvider(server, getRegistry) {
|
|
136
|
-
server.tool(
|
|
143
|
+
server.tool("list_categories", "List all available snippet categories with snippet counts", {}, async () => {
|
|
137
144
|
const registry = await getRegistry();
|
|
138
145
|
const counts = {};
|
|
139
146
|
for (const snippet of registry.snippets) {
|
|
@@ -141,28 +148,31 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
141
148
|
}
|
|
142
149
|
const result = Object.entries(counts)
|
|
143
150
|
.sort(([, a], [, b]) => b - a)
|
|
144
|
-
.map(([cat, count]) => `${cat}: ${count} snippet${count > 1 ?
|
|
145
|
-
.join(
|
|
151
|
+
.map(([cat, count]) => `${cat}: ${count} snippet${count > 1 ? "s" : ""}`)
|
|
152
|
+
.join("\n");
|
|
146
153
|
return {
|
|
147
154
|
content: [
|
|
148
155
|
{
|
|
149
|
-
type:
|
|
156
|
+
type: "text",
|
|
150
157
|
text: `${registry.snippets.length} snippets across ${Object.keys(counts).length} categories:\n\n${result}`,
|
|
151
158
|
},
|
|
152
159
|
],
|
|
153
160
|
};
|
|
154
161
|
});
|
|
155
|
-
server.tool(
|
|
162
|
+
server.tool("search_snippets", "Search Webspire CSS snippets by keyword, problem description, or use case. Returns matching snippets with their descriptions and IDs.", {
|
|
156
163
|
query: z
|
|
157
164
|
.string()
|
|
158
165
|
.describe('Search query: problem description, use case, or CSS technique (e.g. "glass card", "fade animation", "blur entrance")'),
|
|
159
166
|
category: z
|
|
160
167
|
.string()
|
|
161
168
|
.optional()
|
|
162
|
-
.describe(
|
|
163
|
-
tags: z.array(z.string()).optional().describe(
|
|
164
|
-
darkMode: z.boolean().optional().describe(
|
|
165
|
-
responsive: z
|
|
169
|
+
.describe("Filter by category: glass, animations, easing, scroll, decorative, interactions, text"),
|
|
170
|
+
tags: z.array(z.string()).optional().describe("Filter by tags"),
|
|
171
|
+
darkMode: z.boolean().optional().describe("Filter for dark mode support"),
|
|
172
|
+
responsive: z
|
|
173
|
+
.boolean()
|
|
174
|
+
.optional()
|
|
175
|
+
.describe("Filter for responsive support"),
|
|
166
176
|
}, async ({ query, category, tags, darkMode, responsive }) => {
|
|
167
177
|
const registry = await getRegistry();
|
|
168
178
|
const results = searchSnippets(registry, {
|
|
@@ -176,23 +186,23 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
176
186
|
return {
|
|
177
187
|
content: [
|
|
178
188
|
{
|
|
179
|
-
type:
|
|
180
|
-
text:
|
|
189
|
+
type: "text",
|
|
190
|
+
text: "No snippets found matching your query. Try broader keywords or check available categories with list_categories.",
|
|
181
191
|
},
|
|
182
192
|
],
|
|
183
193
|
};
|
|
184
194
|
}
|
|
185
|
-
const text = results.map(formatSnippetBrief).join(
|
|
195
|
+
const text = results.map(formatSnippetBrief).join("\n\n");
|
|
186
196
|
return {
|
|
187
197
|
content: [
|
|
188
198
|
{
|
|
189
|
-
type:
|
|
190
|
-
text: `Found ${results.length} snippet${results.length > 1 ?
|
|
199
|
+
type: "text",
|
|
200
|
+
text: `Found ${results.length} snippet${results.length > 1 ? "s" : ""}:\n\n${text}`,
|
|
191
201
|
},
|
|
192
202
|
],
|
|
193
203
|
};
|
|
194
204
|
});
|
|
195
|
-
server.tool(
|
|
205
|
+
server.tool("get_snippet", "Get full CSS source, metadata, usage example, and custom properties for a specific snippet. Use this after finding a snippet via search_snippets or recommend_snippet.", {
|
|
196
206
|
id: z
|
|
197
207
|
.string()
|
|
198
208
|
.describe('Snippet ID, e.g. "glass/frosted", "animations/fade-in", "easing/tokens"'),
|
|
@@ -200,38 +210,41 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
200
210
|
const registry = await getRegistry();
|
|
201
211
|
const snippet = registry.snippets.find((s) => s.id === id);
|
|
202
212
|
if (!snippet) {
|
|
203
|
-
const available = registry.snippets.map((s) => s.id).join(
|
|
213
|
+
const available = registry.snippets.map((s) => s.id).join(", ");
|
|
204
214
|
return {
|
|
205
215
|
content: [
|
|
206
216
|
{
|
|
207
|
-
type:
|
|
217
|
+
type: "text",
|
|
208
218
|
text: `Snippet "${id}" not found. Available snippets: ${available}`,
|
|
209
219
|
},
|
|
210
220
|
],
|
|
211
221
|
};
|
|
212
222
|
}
|
|
213
223
|
return {
|
|
214
|
-
content: [{ type:
|
|
224
|
+
content: [{ type: "text", text: formatSnippetFull(snippet) }],
|
|
215
225
|
};
|
|
216
226
|
});
|
|
217
|
-
server.tool(
|
|
227
|
+
server.tool("recommend_snippet", "Describe a UI problem or desired effect and get the best matching Webspire snippets with reasoning. Supports optional constraints like dark mode or reduced motion requirements.", {
|
|
218
228
|
use_case: z
|
|
219
229
|
.string()
|
|
220
230
|
.describe('Describe the UI problem or effect you need (e.g. "I need a blurred card overlay on my hero section", "my list items should animate in one by one")'),
|
|
221
|
-
category: z
|
|
231
|
+
category: z
|
|
232
|
+
.string()
|
|
233
|
+
.optional()
|
|
234
|
+
.describe("Limit recommendations to a specific category"),
|
|
222
235
|
needs_dark_mode: z
|
|
223
236
|
.boolean()
|
|
224
237
|
.optional()
|
|
225
|
-
.describe(
|
|
238
|
+
.describe("Only recommend snippets with dark mode support"),
|
|
226
239
|
needs_responsive: z
|
|
227
240
|
.boolean()
|
|
228
241
|
.optional()
|
|
229
|
-
.describe(
|
|
242
|
+
.describe("Only recommend snippets with responsive support"),
|
|
230
243
|
needs_reduced_motion: z
|
|
231
244
|
.boolean()
|
|
232
245
|
.optional()
|
|
233
|
-
.describe(
|
|
234
|
-
}, async ({ use_case, category, needs_dark_mode, needs_responsive, needs_reduced_motion }) => {
|
|
246
|
+
.describe("Only recommend snippets that respect prefers-reduced-motion"),
|
|
247
|
+
}, async ({ use_case, category, needs_dark_mode, needs_responsive, needs_reduced_motion, }) => {
|
|
235
248
|
const registry = await getRegistry();
|
|
236
249
|
let filteredRegistry = registry;
|
|
237
250
|
if (needs_reduced_motion) {
|
|
@@ -251,8 +264,8 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
251
264
|
return {
|
|
252
265
|
content: [
|
|
253
266
|
{
|
|
254
|
-
type:
|
|
255
|
-
text:
|
|
267
|
+
type: "text",
|
|
268
|
+
text: "No matching snippet found for your description. Try rephrasing or relax the constraints.",
|
|
256
269
|
},
|
|
257
270
|
],
|
|
258
271
|
};
|
|
@@ -261,25 +274,25 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
261
274
|
.map((s, i) => {
|
|
262
275
|
const reasons = [];
|
|
263
276
|
if (s.darkMode)
|
|
264
|
-
reasons.push(
|
|
277
|
+
reasons.push("dark mode");
|
|
265
278
|
if (s.meta.accessibility.prefersReducedMotion)
|
|
266
|
-
reasons.push(
|
|
279
|
+
reasons.push("reduced motion");
|
|
267
280
|
if (s.meta.accessibility.supportsCheck)
|
|
268
|
-
reasons.push(
|
|
269
|
-
const reasonStr = reasons.length > 0 ? ` (${reasons.join(
|
|
281
|
+
reasons.push("@supports fallback");
|
|
282
|
+
const reasonStr = reasons.length > 0 ? ` (${reasons.join(", ")})` : "";
|
|
270
283
|
return `${i + 1}. ${formatSnippetBrief(s)}${reasonStr}\n Usage: \`${s.meta.usageExample}\``;
|
|
271
284
|
})
|
|
272
|
-
.join(
|
|
285
|
+
.join("\n\n");
|
|
273
286
|
return {
|
|
274
287
|
content: [
|
|
275
288
|
{
|
|
276
|
-
type:
|
|
277
|
-
text: `Top ${results.length} recommendation${results.length > 1 ?
|
|
289
|
+
type: "text",
|
|
290
|
+
text: `Top ${results.length} recommendation${results.length > 1 ? "s" : ""} for "${use_case}":\n\n${text}\n\nUse get_snippet(id) for full CSS and details.`,
|
|
278
291
|
},
|
|
279
292
|
],
|
|
280
293
|
};
|
|
281
294
|
});
|
|
282
|
-
server.tool(
|
|
295
|
+
server.tool("list_pattern_families", "List all available pattern families with counts", {}, async () => {
|
|
283
296
|
const registry = await getRegistry();
|
|
284
297
|
const patterns = registry.patterns ?? [];
|
|
285
298
|
const counts = {};
|
|
@@ -288,62 +301,54 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
288
301
|
}
|
|
289
302
|
const result = Object.entries(counts)
|
|
290
303
|
.sort(([, a], [, b]) => b - a)
|
|
291
|
-
.map(([family, count]) => `${family}: ${count} pattern${count > 1 ?
|
|
292
|
-
.join(
|
|
304
|
+
.map(([family, count]) => `${family}: ${count} pattern${count > 1 ? "s" : ""}`)
|
|
305
|
+
.join("\n");
|
|
293
306
|
return {
|
|
294
307
|
content: [
|
|
295
308
|
{
|
|
296
|
-
type:
|
|
309
|
+
type: "text",
|
|
297
310
|
text: `${patterns.length} patterns across ${Object.keys(counts).length} families:\n\n${result}`,
|
|
298
311
|
},
|
|
299
312
|
],
|
|
300
313
|
};
|
|
301
314
|
});
|
|
302
|
-
server.tool(
|
|
303
|
-
query: z
|
|
315
|
+
server.tool("search_patterns", "Search Webspire UI patterns by intent, family, tier, domain, tone, or UX goal. Uses weighted scoring with synonym expansion for better results.", {
|
|
316
|
+
query: z
|
|
317
|
+
.string()
|
|
318
|
+
.describe('Search query, e.g. "hero with image", "pricing section", "trust building for legal"'),
|
|
304
319
|
family: z
|
|
305
320
|
.string()
|
|
306
321
|
.optional()
|
|
307
|
-
.describe(
|
|
308
|
-
tier: z.enum([
|
|
309
|
-
|
|
322
|
+
.describe("Filter by pattern family, e.g. hero, pricing, navbar"),
|
|
323
|
+
tier: z.enum(["base", "enhanced"]).optional().describe("Filter by tier"),
|
|
324
|
+
domain: z
|
|
325
|
+
.string()
|
|
326
|
+
.optional()
|
|
327
|
+
.describe("Filter/boost by business domain: legal, healthcare, education, finance, saas, ecommerce, agency, consulting, nonprofit, gastronomy, realestate, personal"),
|
|
328
|
+
tone: z
|
|
329
|
+
.string()
|
|
330
|
+
.optional()
|
|
331
|
+
.describe("Filter/boost by tone: serious, premium, modern, friendly, approachable, approachable_professional, technical, editorial, playful, institutional, industrial, casual"),
|
|
332
|
+
ux_goal: z
|
|
333
|
+
.enum(UX_GOAL_KEYS)
|
|
334
|
+
.optional()
|
|
335
|
+
.describe(`Filter/boost by UX goal: ${UX_GOAL_LABEL}`),
|
|
336
|
+
}, async ({ query, family, tier, domain, tone, ux_goal }) => {
|
|
310
337
|
const registry = await getRegistry();
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const results = patterns
|
|
320
|
-
.map((p) => {
|
|
321
|
-
const haystack = [
|
|
322
|
-
p.title,
|
|
323
|
-
p.summary,
|
|
324
|
-
p.description ?? '',
|
|
325
|
-
p.family,
|
|
326
|
-
p.kind,
|
|
327
|
-
...p.tags,
|
|
328
|
-
...p.search.intent,
|
|
329
|
-
...p.search.keywords,
|
|
330
|
-
...p.search.useCases,
|
|
331
|
-
]
|
|
332
|
-
.join(' ')
|
|
333
|
-
.toLowerCase();
|
|
334
|
-
const score = terms.reduce((acc, t) => acc + (haystack.includes(t) ? 1 : 0), 0);
|
|
335
|
-
return { pattern: p, score };
|
|
336
|
-
})
|
|
337
|
-
.filter((row) => row.score > 0)
|
|
338
|
-
.sort((a, b) => b.score - a.score)
|
|
339
|
-
.slice(0, 10)
|
|
340
|
-
.map((row) => row.pattern);
|
|
338
|
+
const results = searchPatterns(registry.patterns ?? [], {
|
|
339
|
+
query,
|
|
340
|
+
family,
|
|
341
|
+
tier,
|
|
342
|
+
domain,
|
|
343
|
+
tone,
|
|
344
|
+
uxGoal: ux_goal,
|
|
345
|
+
});
|
|
341
346
|
if (results.length === 0) {
|
|
342
347
|
return {
|
|
343
348
|
content: [
|
|
344
349
|
{
|
|
345
|
-
type:
|
|
346
|
-
text:
|
|
350
|
+
type: "text",
|
|
351
|
+
text: "No patterns found matching your query.",
|
|
347
352
|
},
|
|
348
353
|
],
|
|
349
354
|
};
|
|
@@ -351,23 +356,88 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
351
356
|
return {
|
|
352
357
|
content: [
|
|
353
358
|
{
|
|
354
|
-
type:
|
|
355
|
-
text: `Found ${results.length} pattern${results.length > 1 ?
|
|
359
|
+
type: "text",
|
|
360
|
+
text: `Found ${results.length} pattern${results.length > 1 ? "s" : ""}:\n\n${results.map(formatPatternBrief).join("\n\n")}`,
|
|
356
361
|
},
|
|
357
362
|
],
|
|
358
363
|
};
|
|
359
364
|
});
|
|
360
|
-
server.tool(
|
|
361
|
-
|
|
365
|
+
server.tool("compose_page", "Compose an optimal page structure by selecting and ordering patterns based on business domain, tone, and UX goals. Also recommends matching CSS snippets for polish and interaction.", {
|
|
366
|
+
domain: z.enum(DOMAIN_KEYS).describe(`Business domain: ${DOMAIN_LABEL}`),
|
|
367
|
+
tone: z.enum(TONE_KEYS).describe(`Desired tone: ${TONE_LABEL}`),
|
|
368
|
+
ux_goals: z
|
|
369
|
+
.array(z.enum(UX_GOAL_KEYS))
|
|
370
|
+
.describe(`UX goals: ${UX_GOAL_LABEL}`),
|
|
371
|
+
content_needs: z
|
|
372
|
+
.array(z.enum(CONTENT_NEED_KEYS))
|
|
373
|
+
.optional()
|
|
374
|
+
.describe(`Optional explicit content requirements. Allowed values: ${CONTENT_NEED_LABEL}`),
|
|
375
|
+
max_sections: z
|
|
376
|
+
.number()
|
|
377
|
+
.optional()
|
|
378
|
+
.default(8)
|
|
379
|
+
.describe("Maximum number of sections to include (default: 8)"),
|
|
380
|
+
}, async ({ domain, tone, ux_goals, content_needs, max_sections }) => {
|
|
381
|
+
const registry = await getRegistry();
|
|
382
|
+
const allPatterns = registry.patterns ?? [];
|
|
383
|
+
const result = composePage(allPatterns, registry.snippets, {
|
|
384
|
+
domain,
|
|
385
|
+
tone,
|
|
386
|
+
uxGoals: ux_goals,
|
|
387
|
+
contentNeeds: content_needs
|
|
388
|
+
?.map(normalizeContentNeed)
|
|
389
|
+
.filter((need) => need !== null),
|
|
390
|
+
maxSections: max_sections,
|
|
391
|
+
});
|
|
392
|
+
if (result.patterns.length === 0) {
|
|
393
|
+
return {
|
|
394
|
+
content: [
|
|
395
|
+
{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: `No suitable patterns found for domain="${domain}", tone="${tone}". Try broader criteria.`,
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const sections = result.patterns
|
|
403
|
+
.map((p, i) => {
|
|
404
|
+
const role = p.ai?.compositionRole ?? "supporting";
|
|
405
|
+
return `${i + 1}. **${p.title}** (${p.id}) — role: ${role}\n ${p.summary}`;
|
|
406
|
+
})
|
|
407
|
+
.join("\n\n");
|
|
408
|
+
const reasoningText = result.reasoning.length > 0
|
|
409
|
+
? `\n\n## Selection Reasoning\n${result.reasoning.join("\n")}`
|
|
410
|
+
: "";
|
|
411
|
+
const warningsText = result.warnings.length > 0
|
|
412
|
+
? `\n\n## Warnings\n${result.warnings.join("\n")}`
|
|
413
|
+
: "";
|
|
414
|
+
const snippetsText = result.snippets.length > 0
|
|
415
|
+
? `\n\n## Recommended Snippets\n${result.snippets
|
|
416
|
+
.map(({ snippet, reason }, i) => `${i + 1}. **${snippet.title}** (${snippet.id}) — ${reason}`)
|
|
417
|
+
.join("\n")}\n\nUse \`get_snippet(id)\` to retrieve the CSS for any snippet.`
|
|
418
|
+
: "";
|
|
419
|
+
return {
|
|
420
|
+
content: [
|
|
421
|
+
{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: `# Composed Page: ${domain} / ${tone}\n\nUX Goals: ${ux_goals.join(", ")}${content_needs?.length ? `\nContent Needs: ${content_needs.join(", ")}` : ""}\n\n## Recommended Sections\n\n${sections}${snippetsText}${warningsText}${reasoningText}\n\nUse \`get_pattern(id)\` to get full HTML for each section.`,
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
server.tool("get_pattern", "Get full pattern data including HTML (and optional CSS/JS) for a specific pattern. Patterns use --ws-* design tokens via component classes — import webspire-tokens.css + webspire-components.css to enable.", {
|
|
429
|
+
id: z
|
|
430
|
+
.string()
|
|
431
|
+
.describe('Pattern ID, e.g. "hero/base", "hero/with-image"'),
|
|
362
432
|
}, async ({ id }) => {
|
|
363
433
|
const registry = await getRegistry();
|
|
364
434
|
const pattern = (registry.patterns ?? []).find((p) => p.id === id);
|
|
365
435
|
if (!pattern) {
|
|
366
|
-
const available = (registry.patterns ?? []).map((p) => p.id).join(
|
|
436
|
+
const available = (registry.patterns ?? []).map((p) => p.id).join(", ");
|
|
367
437
|
return {
|
|
368
438
|
content: [
|
|
369
439
|
{
|
|
370
|
-
type:
|
|
440
|
+
type: "text",
|
|
371
441
|
text: `Pattern "${id}" not found. Available patterns: ${available}`,
|
|
372
442
|
},
|
|
373
443
|
],
|
|
@@ -376,13 +446,13 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
376
446
|
return {
|
|
377
447
|
content: [
|
|
378
448
|
{
|
|
379
|
-
type:
|
|
449
|
+
type: "text",
|
|
380
450
|
text: `${formatPatternFull(pattern)}\n\n---\n**Setup:** Import \`webspire-tokens.css\` and \`webspire-components.css\` to enable token support.\nOverride \`--ws-color-primary\` etc. to match your brand. Docs: https://webspire.de/tokens`,
|
|
381
451
|
},
|
|
382
452
|
],
|
|
383
453
|
};
|
|
384
454
|
});
|
|
385
|
-
server.tool(
|
|
455
|
+
server.tool("list_templates", "List all available page templates grouped by category", {}, async () => {
|
|
386
456
|
const registry = await getRegistry();
|
|
387
457
|
const templates = registry.templates ?? [];
|
|
388
458
|
const counts = {};
|
|
@@ -391,27 +461,29 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
391
461
|
}
|
|
392
462
|
const result = Object.entries(counts)
|
|
393
463
|
.sort(([, a], [, b]) => b - a)
|
|
394
|
-
.map(([cat, count]) => `${cat}: ${count} template${count > 1 ?
|
|
395
|
-
.join(
|
|
464
|
+
.map(([cat, count]) => `${cat}: ${count} template${count > 1 ? "s" : ""}`)
|
|
465
|
+
.join("\n");
|
|
396
466
|
return {
|
|
397
467
|
content: [
|
|
398
468
|
{
|
|
399
|
-
type:
|
|
469
|
+
type: "text",
|
|
400
470
|
text: `${templates.length} templates across ${Object.keys(counts).length} categories:\n\n${result}`,
|
|
401
471
|
},
|
|
402
472
|
],
|
|
403
473
|
};
|
|
404
474
|
});
|
|
405
|
-
server.tool(
|
|
406
|
-
query: z
|
|
475
|
+
server.tool("search_templates", "Search Webspire page templates by keyword, category, or style.", {
|
|
476
|
+
query: z
|
|
477
|
+
.string()
|
|
478
|
+
.describe('Search query, e.g. "saas landing", "portfolio dark", "shop"'),
|
|
407
479
|
category: z
|
|
408
480
|
.string()
|
|
409
481
|
.optional()
|
|
410
|
-
.describe(
|
|
482
|
+
.describe("Filter by category, e.g. saas-landing, agency, shop"),
|
|
411
483
|
style: z
|
|
412
484
|
.string()
|
|
413
485
|
.optional()
|
|
414
|
-
.describe(
|
|
486
|
+
.describe("Filter by style, e.g. modern, bold, minimal, corporate"),
|
|
415
487
|
}, async ({ query, category, style }) => {
|
|
416
488
|
const registry = await getRegistry();
|
|
417
489
|
let templates = registry.templates ?? [];
|
|
@@ -425,13 +497,13 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
425
497
|
const haystack = [
|
|
426
498
|
t.title,
|
|
427
499
|
t.summary,
|
|
428
|
-
t.description ??
|
|
500
|
+
t.description ?? "",
|
|
429
501
|
t.category,
|
|
430
502
|
t.style,
|
|
431
503
|
...t.tags,
|
|
432
504
|
...t.sections,
|
|
433
505
|
]
|
|
434
|
-
.join(
|
|
506
|
+
.join(" ")
|
|
435
507
|
.toLowerCase();
|
|
436
508
|
const score = terms.reduce((acc, term) => acc + (haystack.includes(term) ? 1 : 0), 0);
|
|
437
509
|
return { template: t, score };
|
|
@@ -442,46 +514,57 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
442
514
|
.map((row) => row.template);
|
|
443
515
|
if (results.length === 0) {
|
|
444
516
|
return {
|
|
445
|
-
content: [
|
|
517
|
+
content: [
|
|
518
|
+
{
|
|
519
|
+
type: "text",
|
|
520
|
+
text: "No templates found matching your query.",
|
|
521
|
+
},
|
|
522
|
+
],
|
|
446
523
|
};
|
|
447
524
|
}
|
|
448
525
|
return {
|
|
449
526
|
content: [
|
|
450
527
|
{
|
|
451
|
-
type:
|
|
452
|
-
text: `Found ${results.length} template${results.length > 1 ?
|
|
528
|
+
type: "text",
|
|
529
|
+
text: `Found ${results.length} template${results.length > 1 ? "s" : ""}:\n\n${results.map(formatTemplateBrief).join("\n\n")}`,
|
|
453
530
|
},
|
|
454
531
|
],
|
|
455
532
|
};
|
|
456
533
|
});
|
|
457
|
-
server.tool(
|
|
458
|
-
id: z
|
|
534
|
+
server.tool("get_template", "Get full template HTML for a specific page template. Returns standalone HTML ready to use.", {
|
|
535
|
+
id: z
|
|
536
|
+
.string()
|
|
537
|
+
.describe('Template ID, e.g. "saas-landing/modern", "shop/catalog"'),
|
|
459
538
|
}, async ({ id }) => {
|
|
460
539
|
const registry = await getRegistry();
|
|
461
540
|
const template = (registry.templates ?? []).find((t) => t.id === id);
|
|
462
541
|
if (!template) {
|
|
463
|
-
const available = (registry.templates ?? [])
|
|
542
|
+
const available = (registry.templates ?? [])
|
|
543
|
+
.map((t) => t.id)
|
|
544
|
+
.join(", ");
|
|
464
545
|
return {
|
|
465
546
|
content: [
|
|
466
547
|
{
|
|
467
|
-
type:
|
|
548
|
+
type: "text",
|
|
468
549
|
text: `Template "${id}" not found. Available templates: ${available}`,
|
|
469
550
|
},
|
|
470
551
|
],
|
|
471
552
|
};
|
|
472
553
|
}
|
|
473
554
|
return {
|
|
474
|
-
content: [
|
|
555
|
+
content: [
|
|
556
|
+
{ type: "text", text: formatTemplateFull(template) },
|
|
557
|
+
],
|
|
475
558
|
};
|
|
476
559
|
});
|
|
477
|
-
server.tool(
|
|
560
|
+
server.tool("recommend_token_mapping", "Analyze project design tokens and recommend how to map them to Webspire --ws-* tokens. Provide your existing CSS custom properties or Tailwind theme colors.", {
|
|
478
561
|
project_tokens: z
|
|
479
562
|
.string()
|
|
480
563
|
.describe('Your project\'s design tokens or CSS custom properties, e.g. "--color-brand-500: #2563eb; --color-bg: #fafafa" or "primary: blue-600, background: white"'),
|
|
481
564
|
framework: z
|
|
482
|
-
.enum([
|
|
565
|
+
.enum(["tailwind", "custom", "bootstrap", "chakra", "shadcn"])
|
|
483
566
|
.optional()
|
|
484
|
-
.describe(
|
|
567
|
+
.describe("CSS framework used in your project"),
|
|
485
568
|
}, async ({ project_tokens, framework }) => {
|
|
486
569
|
const lines = project_tokens
|
|
487
570
|
.split(/[;\n,]+/)
|
|
@@ -491,101 +574,107 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
491
574
|
const TOKEN_HINTS = [
|
|
492
575
|
{
|
|
493
576
|
patterns: [/brand|primary|main|accent-color/i],
|
|
494
|
-
wsToken:
|
|
495
|
-
description:
|
|
577
|
+
wsToken: "--ws-color-primary",
|
|
578
|
+
description: "Primary/brand color",
|
|
496
579
|
},
|
|
497
580
|
{
|
|
498
581
|
patterns: [/brand.*hover|primary.*hover|primary.*dark/i],
|
|
499
|
-
wsToken:
|
|
500
|
-
description:
|
|
582
|
+
wsToken: "--ws-color-primary-hover",
|
|
583
|
+
description: "Primary hover state",
|
|
501
584
|
},
|
|
502
585
|
{
|
|
503
586
|
patterns: [/brand.*light|primary.*light|primary.*50|brand.*50/i],
|
|
504
|
-
wsToken:
|
|
505
|
-
description:
|
|
587
|
+
wsToken: "--ws-color-primary-soft",
|
|
588
|
+
description: "Primary soft background",
|
|
506
589
|
},
|
|
507
590
|
{
|
|
508
591
|
patterns: [/secondary|accent(?!-color)/i],
|
|
509
|
-
wsToken:
|
|
510
|
-
description:
|
|
592
|
+
wsToken: "--ws-color-accent",
|
|
593
|
+
description: "Secondary/accent color",
|
|
511
594
|
},
|
|
512
595
|
{
|
|
513
596
|
patterns: [/^bg$|background(?!.*secondary)|surface(?!.*alt)/i],
|
|
514
|
-
wsToken:
|
|
515
|
-
description:
|
|
597
|
+
wsToken: "--ws-color-surface",
|
|
598
|
+
description: "Page background",
|
|
516
599
|
},
|
|
517
600
|
{
|
|
518
|
-
patterns: [
|
|
519
|
-
|
|
520
|
-
|
|
601
|
+
patterns: [
|
|
602
|
+
/bg.*secondary|bg.*subtle|bg.*muted|surface.*alt|bg.*light/i,
|
|
603
|
+
],
|
|
604
|
+
wsToken: "--ws-color-surface-alt",
|
|
605
|
+
description: "Subtle background",
|
|
521
606
|
},
|
|
522
607
|
{
|
|
523
608
|
patterns: [/^text$|text.*primary|foreground(?!.*muted)/i],
|
|
524
|
-
wsToken:
|
|
525
|
-
description:
|
|
609
|
+
wsToken: "--ws-color-text",
|
|
610
|
+
description: "Primary text color",
|
|
526
611
|
},
|
|
527
612
|
{
|
|
528
|
-
patterns: [
|
|
529
|
-
|
|
530
|
-
|
|
613
|
+
patterns: [
|
|
614
|
+
/text.*secondary|text.*soft|text.*muted|muted.*foreground/i,
|
|
615
|
+
],
|
|
616
|
+
wsToken: "--ws-color-text-muted",
|
|
617
|
+
description: "Muted text color",
|
|
531
618
|
},
|
|
532
619
|
{
|
|
533
620
|
patterns: [/border(?!.*strong)|divider/i],
|
|
534
|
-
wsToken:
|
|
535
|
-
description:
|
|
621
|
+
wsToken: "--ws-color-border",
|
|
622
|
+
description: "Default border",
|
|
536
623
|
},
|
|
537
624
|
{
|
|
538
625
|
patterns: [/success|green|positive/i],
|
|
539
|
-
wsToken:
|
|
540
|
-
description:
|
|
626
|
+
wsToken: "--ws-color-success",
|
|
627
|
+
description: "Success color",
|
|
541
628
|
},
|
|
542
629
|
{
|
|
543
630
|
patterns: [/warning|amber|yellow|caution/i],
|
|
544
|
-
wsToken:
|
|
545
|
-
description:
|
|
631
|
+
wsToken: "--ws-color-warning",
|
|
632
|
+
description: "Warning color",
|
|
546
633
|
},
|
|
547
634
|
{
|
|
548
635
|
patterns: [/danger|error|red|destructive/i],
|
|
549
|
-
wsToken:
|
|
550
|
-
description:
|
|
636
|
+
wsToken: "--ws-color-danger",
|
|
637
|
+
description: "Danger/error color",
|
|
551
638
|
},
|
|
552
639
|
{
|
|
553
640
|
patterns: [/radius|rounded|corner/i],
|
|
554
|
-
wsToken:
|
|
555
|
-
description:
|
|
641
|
+
wsToken: "--ws-radius-md",
|
|
642
|
+
description: "Border radius",
|
|
556
643
|
},
|
|
557
644
|
];
|
|
558
645
|
for (const line of lines) {
|
|
559
|
-
const [rawName] = line.split(
|
|
560
|
-
const name = rawName.replace(/^--color-|^--/,
|
|
646
|
+
const [rawName] = line.split(":").map((s) => s.trim());
|
|
647
|
+
const name = rawName.replace(/^--color-|^--/, "");
|
|
561
648
|
for (const hint of TOKEN_HINTS) {
|
|
562
649
|
if (hint.patterns.some((p) => p.test(name))) {
|
|
563
|
-
const varRef = rawName.startsWith(
|
|
650
|
+
const varRef = rawName.startsWith("--")
|
|
651
|
+
? `var(${rawName})`
|
|
652
|
+
: rawName;
|
|
564
653
|
mappings.push(` ${hint.wsToken}: ${varRef}; /* ${hint.description} ← ${rawName} */`);
|
|
565
654
|
break;
|
|
566
655
|
}
|
|
567
656
|
}
|
|
568
657
|
}
|
|
569
658
|
const frameworkNote = framework
|
|
570
|
-
? `\nFramework detected: ${framework}. ${framework ===
|
|
571
|
-
?
|
|
572
|
-
: framework ===
|
|
573
|
-
?
|
|
574
|
-
:
|
|
575
|
-
:
|
|
659
|
+
? `\nFramework detected: ${framework}. ${framework === "tailwind"
|
|
660
|
+
? "Use @theme to register --ws-* tokens, or override in your main CSS."
|
|
661
|
+
: framework === "shadcn"
|
|
662
|
+
? "Map shadcn/ui --* variables to --ws-* in your globals.css."
|
|
663
|
+
: "Override --ws-* tokens in your global stylesheet."}\n`
|
|
664
|
+
: "";
|
|
576
665
|
const css = mappings.length > 0
|
|
577
|
-
? `:root {\n${mappings.join(
|
|
578
|
-
:
|
|
666
|
+
? `:root {\n${mappings.join("\n")}\n}`
|
|
667
|
+
: "/* No matching tokens detected. Provide CSS custom properties like:\n --color-brand: #2563eb; --color-bg: #ffffff */";
|
|
579
668
|
return {
|
|
580
669
|
content: [
|
|
581
670
|
{
|
|
582
|
-
type:
|
|
671
|
+
type: "text",
|
|
583
672
|
text: `# Recommended Token Mapping\n${frameworkNote}\n\`\`\`css\n${css}\n\`\`\`\n\nPaste this into your project's CSS, then import webspire-tokens.css.\nAdjust values as needed — these are suggestions based on naming conventions.\n\nFull token reference: https://webspire.de/tokens`,
|
|
584
673
|
},
|
|
585
674
|
],
|
|
586
675
|
};
|
|
587
676
|
});
|
|
588
|
-
server.tool(
|
|
677
|
+
server.tool("setup_tokens", "Get the WebSpire token CSS files to write into your project. Returns the content of webspire-tokens.css (alias tokens + dark mode) and webspire-components.css (per-component tokens). Write these files to your project and import them in your main CSS.", {
|
|
589
678
|
components: z
|
|
590
679
|
.array(z.string())
|
|
591
680
|
.optional()
|
|
@@ -593,29 +682,31 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
593
682
|
}, async ({ components }) => {
|
|
594
683
|
const registry = await getRegistry();
|
|
595
684
|
// Read bundled CSS files
|
|
596
|
-
const { readFile } = await import(
|
|
597
|
-
const { dirname, resolve } = await import(
|
|
598
|
-
const { fileURLToPath } = await import(
|
|
685
|
+
const { readFile } = await import("node:fs/promises");
|
|
686
|
+
const { dirname, resolve } = await import("node:path");
|
|
687
|
+
const { fileURLToPath } = await import("node:url");
|
|
599
688
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
600
689
|
let tokensCss;
|
|
601
690
|
let componentsCss;
|
|
602
691
|
try {
|
|
603
|
-
tokensCss = await readFile(resolve(thisDir,
|
|
604
|
-
componentsCss = await readFile(resolve(thisDir,
|
|
692
|
+
tokensCss = await readFile(resolve(thisDir, "..", "css", "webspire-tokens.css"), "utf-8");
|
|
693
|
+
componentsCss = await readFile(resolve(thisDir, "..", "css", "webspire-components.css"), "utf-8");
|
|
605
694
|
}
|
|
606
695
|
catch {
|
|
607
696
|
return {
|
|
608
697
|
content: [
|
|
609
698
|
{
|
|
610
|
-
type:
|
|
611
|
-
text:
|
|
699
|
+
type: "text",
|
|
700
|
+
text: "Token CSS files not found in package. Update @webspire/mcp to the latest version.",
|
|
612
701
|
},
|
|
613
702
|
],
|
|
614
703
|
};
|
|
615
704
|
}
|
|
616
705
|
// Filter component tokens if specific families requested
|
|
617
706
|
if (components && components.length > 0) {
|
|
618
|
-
const filtered = [
|
|
707
|
+
const filtered = [
|
|
708
|
+
"/* Webspire Component Tokens (filtered) */",
|
|
709
|
+
];
|
|
619
710
|
const sections = componentsCss.split(/(?=^\.ws-)/m);
|
|
620
711
|
for (const section of sections) {
|
|
621
712
|
const classMatch = section.match(/^\.ws-([a-z0-9-]+)/);
|
|
@@ -623,56 +714,56 @@ export function registerToolsWithProvider(server, getRegistry) {
|
|
|
623
714
|
filtered.push(section.trim());
|
|
624
715
|
}
|
|
625
716
|
}
|
|
626
|
-
componentsCss = filtered.join(
|
|
717
|
+
componentsCss = filtered.join("\n\n");
|
|
627
718
|
}
|
|
628
|
-
const families = components ? components.join(
|
|
719
|
+
const families = components ? components.join(", ") : "all";
|
|
629
720
|
const patternCount = registry.patterns?.length ?? 0;
|
|
630
721
|
return {
|
|
631
722
|
content: [
|
|
632
723
|
{
|
|
633
|
-
type:
|
|
724
|
+
type: "text",
|
|
634
725
|
text: [
|
|
635
726
|
`# WebSpire Token Setup (${families} families, ${patternCount} patterns)`,
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
727
|
+
"",
|
|
728
|
+
"## 1. Write `webspire-tokens.css` to your project",
|
|
729
|
+
"",
|
|
730
|
+
"```css",
|
|
640
731
|
tokensCss,
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
732
|
+
"```",
|
|
733
|
+
"",
|
|
734
|
+
"## 2. Write `webspire-components.css` to your project",
|
|
735
|
+
"",
|
|
736
|
+
"```css",
|
|
646
737
|
componentsCss,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
738
|
+
"```",
|
|
739
|
+
"",
|
|
740
|
+
"## 3. Import in your main CSS",
|
|
741
|
+
"",
|
|
742
|
+
"```css",
|
|
652
743
|
'@import "./webspire-tokens.css";',
|
|
653
744
|
'@import "./webspire-components.css";',
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
].join(
|
|
745
|
+
"```",
|
|
746
|
+
"",
|
|
747
|
+
"## 4. Override to match your brand",
|
|
748
|
+
"",
|
|
749
|
+
"```css",
|
|
750
|
+
":root {",
|
|
751
|
+
" --ws-color-primary: #your-brand-color;",
|
|
752
|
+
" --ws-color-primary-hover: #your-brand-darker;",
|
|
753
|
+
"}",
|
|
754
|
+
"```",
|
|
755
|
+
"",
|
|
756
|
+
"Docs: https://webspire.de/tokens",
|
|
757
|
+
].join("\n"),
|
|
667
758
|
},
|
|
668
759
|
],
|
|
669
760
|
};
|
|
670
761
|
});
|
|
671
762
|
}
|
|
672
763
|
export function registerResourcesWithProvider(server, getRegistry) {
|
|
673
|
-
server.resource(
|
|
674
|
-
description:
|
|
675
|
-
mimeType:
|
|
764
|
+
server.resource("categories", "webspire://categories", {
|
|
765
|
+
description: "List all snippet categories with counts",
|
|
766
|
+
mimeType: "application/json",
|
|
676
767
|
}, async () => {
|
|
677
768
|
const registry = await getRegistry();
|
|
678
769
|
const counts = {};
|
|
@@ -682,18 +773,18 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
682
773
|
return {
|
|
683
774
|
contents: [
|
|
684
775
|
{
|
|
685
|
-
uri:
|
|
686
|
-
mimeType:
|
|
776
|
+
uri: "webspire://categories",
|
|
777
|
+
mimeType: "application/json",
|
|
687
778
|
text: JSON.stringify(counts, null, 2),
|
|
688
779
|
},
|
|
689
780
|
],
|
|
690
781
|
};
|
|
691
782
|
});
|
|
692
|
-
server.resource(
|
|
693
|
-
description:
|
|
694
|
-
mimeType:
|
|
783
|
+
server.resource("category", "webspire://category/{name}", {
|
|
784
|
+
description: "List snippets in a specific category",
|
|
785
|
+
mimeType: "application/json",
|
|
695
786
|
}, async (uri) => {
|
|
696
|
-
const name = uri.pathname.replace(
|
|
787
|
+
const name = uri.pathname.replace("//", "").split("/").pop();
|
|
697
788
|
const registry = await getRegistry();
|
|
698
789
|
const snippets = registry.snippets.filter((s) => s.category === name);
|
|
699
790
|
const brief = snippets.map((s) => ({
|
|
@@ -708,18 +799,18 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
708
799
|
contents: [
|
|
709
800
|
{
|
|
710
801
|
uri: uri.href,
|
|
711
|
-
mimeType:
|
|
802
|
+
mimeType: "application/json",
|
|
712
803
|
text: JSON.stringify(brief, null, 2),
|
|
713
804
|
},
|
|
714
805
|
],
|
|
715
806
|
};
|
|
716
807
|
});
|
|
717
|
-
server.resource(
|
|
718
|
-
description:
|
|
719
|
-
mimeType:
|
|
808
|
+
server.resource("snippet", "webspire://snippet/{id}", {
|
|
809
|
+
description: "Get full snippet data including CSS source",
|
|
810
|
+
mimeType: "application/json",
|
|
720
811
|
}, async (uri) => {
|
|
721
|
-
const parts = uri.pathname.replace(
|
|
722
|
-
const id = parts.slice(1).join(
|
|
812
|
+
const parts = uri.pathname.replace("//", "").split("/");
|
|
813
|
+
const id = parts.slice(1).join("/");
|
|
723
814
|
const registry = await getRegistry();
|
|
724
815
|
const snippet = registry.snippets.find((s) => s.id === id);
|
|
725
816
|
if (!snippet) {
|
|
@@ -727,7 +818,7 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
727
818
|
contents: [
|
|
728
819
|
{
|
|
729
820
|
uri: uri.href,
|
|
730
|
-
mimeType:
|
|
821
|
+
mimeType: "text/plain",
|
|
731
822
|
text: `Snippet "${id}" not found`,
|
|
732
823
|
},
|
|
733
824
|
],
|
|
@@ -737,13 +828,13 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
737
828
|
contents: [
|
|
738
829
|
{
|
|
739
830
|
uri: uri.href,
|
|
740
|
-
mimeType:
|
|
831
|
+
mimeType: "application/json",
|
|
741
832
|
text: JSON.stringify(snippet, null, 2),
|
|
742
833
|
},
|
|
743
834
|
],
|
|
744
835
|
};
|
|
745
836
|
});
|
|
746
|
-
server.resource(
|
|
837
|
+
server.resource("patterns", "webspire://patterns", { description: "List all patterns", mimeType: "application/json" }, async () => {
|
|
747
838
|
const registry = await getRegistry();
|
|
748
839
|
const patterns = (registry.patterns ?? []).map((p) => ({
|
|
749
840
|
id: p.id,
|
|
@@ -756,19 +847,19 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
756
847
|
return {
|
|
757
848
|
contents: [
|
|
758
849
|
{
|
|
759
|
-
uri:
|
|
760
|
-
mimeType:
|
|
850
|
+
uri: "webspire://patterns",
|
|
851
|
+
mimeType: "application/json",
|
|
761
852
|
text: JSON.stringify(patterns, null, 2),
|
|
762
853
|
},
|
|
763
854
|
],
|
|
764
855
|
};
|
|
765
856
|
});
|
|
766
|
-
server.resource(
|
|
767
|
-
description:
|
|
768
|
-
mimeType:
|
|
857
|
+
server.resource("pattern", "webspire://pattern/{id}", {
|
|
858
|
+
description: "Get full pattern data including HTML source",
|
|
859
|
+
mimeType: "application/json",
|
|
769
860
|
}, async (uri) => {
|
|
770
|
-
const parts = uri.pathname.replace(
|
|
771
|
-
const id = parts.slice(1).join(
|
|
861
|
+
const parts = uri.pathname.replace("//", "").split("/");
|
|
862
|
+
const id = parts.slice(1).join("/");
|
|
772
863
|
const registry = await getRegistry();
|
|
773
864
|
const pattern = (registry.patterns ?? []).find((p) => p.id === id);
|
|
774
865
|
if (!pattern) {
|
|
@@ -776,7 +867,7 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
776
867
|
contents: [
|
|
777
868
|
{
|
|
778
869
|
uri: uri.href,
|
|
779
|
-
mimeType:
|
|
870
|
+
mimeType: "text/plain",
|
|
780
871
|
text: `Pattern "${id}" not found`,
|
|
781
872
|
},
|
|
782
873
|
],
|
|
@@ -786,13 +877,13 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
786
877
|
contents: [
|
|
787
878
|
{
|
|
788
879
|
uri: uri.href,
|
|
789
|
-
mimeType:
|
|
880
|
+
mimeType: "application/json",
|
|
790
881
|
text: JSON.stringify(pattern, null, 2),
|
|
791
882
|
},
|
|
792
883
|
],
|
|
793
884
|
};
|
|
794
885
|
});
|
|
795
|
-
server.resource(
|
|
886
|
+
server.resource("templates", "webspire://templates", { description: "List all page templates", mimeType: "application/json" }, async () => {
|
|
796
887
|
const registry = await getRegistry();
|
|
797
888
|
const templates = (registry.templates ?? []).map((t) => ({
|
|
798
889
|
id: t.id,
|
|
@@ -804,19 +895,19 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
804
895
|
return {
|
|
805
896
|
contents: [
|
|
806
897
|
{
|
|
807
|
-
uri:
|
|
808
|
-
mimeType:
|
|
898
|
+
uri: "webspire://templates",
|
|
899
|
+
mimeType: "application/json",
|
|
809
900
|
text: JSON.stringify(templates, null, 2),
|
|
810
901
|
},
|
|
811
902
|
],
|
|
812
903
|
};
|
|
813
904
|
});
|
|
814
|
-
server.resource(
|
|
815
|
-
description:
|
|
816
|
-
mimeType:
|
|
905
|
+
server.resource("template", "webspire://template/{id}", {
|
|
906
|
+
description: "Get full template data including HTML source",
|
|
907
|
+
mimeType: "application/json",
|
|
817
908
|
}, async (uri) => {
|
|
818
|
-
const parts = uri.pathname.replace(
|
|
819
|
-
const id = parts.slice(1).join(
|
|
909
|
+
const parts = uri.pathname.replace("//", "").split("/");
|
|
910
|
+
const id = parts.slice(1).join("/");
|
|
820
911
|
const registry = await getRegistry();
|
|
821
912
|
const template = (registry.templates ?? []).find((t) => t.id === id);
|
|
822
913
|
if (!template) {
|
|
@@ -824,7 +915,7 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
824
915
|
contents: [
|
|
825
916
|
{
|
|
826
917
|
uri: uri.href,
|
|
827
|
-
mimeType:
|
|
918
|
+
mimeType: "text/plain",
|
|
828
919
|
text: `Template "${id}" not found`,
|
|
829
920
|
},
|
|
830
921
|
],
|
|
@@ -834,7 +925,7 @@ export function registerResourcesWithProvider(server, getRegistry) {
|
|
|
834
925
|
contents: [
|
|
835
926
|
{
|
|
836
927
|
uri: uri.href,
|
|
837
|
-
mimeType:
|
|
928
|
+
mimeType: "application/json",
|
|
838
929
|
text: JSON.stringify(template, null, 2),
|
|
839
930
|
},
|
|
840
931
|
],
|